diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000000..b9c8d9b70f
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,94 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL Advanced"
+
+on:
+ push:
+ branches: [ "release/6.0" ]
+ pull_request:
+ branches: [ "release/6.0" ]
+ schedule:
+ - cron: '33 23 * * 6'
+
+jobs:
+ analyze:
+ name: Analyze (${{ matrix.language }})
+ # Runner size impacts CodeQL analysis time. To learn more, please see:
+ # - https://gh.io/recommended-hardware-resources-for-running-codeql
+ # - https://gh.io/supported-runners-and-hardware-resources
+ # - https://gh.io/using-larger-runners (GitHub.com only)
+ # Consider using larger runners or machines with greater resources for possible analysis time improvements.
+ runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
+ permissions:
+ # required for all workflows
+ security-events: write
+
+ # required to fetch internal or private CodeQL packs
+ packages: read
+
+ # only required for workflows in private repositories
+ actions: read
+ contents: read
+
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - language: csharp
+ build-mode: manual
+ # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
+ # Use `c-cpp` to analyze code written in C, C++ or both
+ # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
+ # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
+ # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
+ # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
+ # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
+ # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Setup .NET Core SDK
+ uses: actions/setup-dotnet@v5.0.1
+ with:
+ global-json-file: global.json
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+
+ # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
+ # queries: security-extended,security-and-quality
+
+ # If the analyze step fails for one of the languages you are analyzing with
+ # "We were unable to automatically build your code", modify the matrix above
+ # to set the build mode to "manual" for that language. Then modify this step
+ # to build your code.
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
+ - name: Run manual build steps
+ if: matrix.build-mode == 'manual'
+ shell: bash
+ run: |
+ mkdir packages
+ dotnet build src/Microsoft.Data.SqlClient.sln
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: "/language:${{matrix.language}}"
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 b76a0099b2..76a9b07939 100644
--- a/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml
+++ b/eng/pipelines/common/templates/jobs/ci-code-coverage-job.yml
@@ -61,10 +61,10 @@ jobs:
displayName: '[Debug] Show Disk Usage'
- task: UseDotNet@2
- displayName: 'Use .NET SDK 8.0.x'
+ displayName: 'Install .NET SDK'
inputs:
packageType: sdk
- version: 8.0.x
+ useGlobalJson: true
- pwsh: |
dotnet tool install --global dotnet-coverage
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 e113692ab9..ebdd99b3c6 100644
--- a/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml
+++ b/eng/pipelines/common/templates/jobs/ci-run-tests-job.yml
@@ -232,18 +232,21 @@ jobs:
dotnet --info
Invoke-WebRequest https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1
-
- # install .net x86
+
$version = "LTS"
if (!"${{parameters.targetFramework }}".StartsWith("net4"))
{
$version = "${{parameters.targetFramework }}".Substring(3, "${{parameters.targetFramework }}".Length-3)
}
+ # Install targetFramework specific .NET runtime (and sdk)
.\dotnet-install.ps1 -Channel $version -Architecture x86 -InstallDir "$(dotnetx86RootPath)"
+ # Install globally required .NET sdk
+ .\dotnet-install.ps1 -Architecture x86 -InstallDir "$(dotnetx86RootPath)" -JSonFile global.json
+
$(dotnetx86RootPath)dotnet.exe --info
- displayName: 'Install .NET x86 '
+ displayName: 'Install .NET x86'
condition: ne(variables['dotnetx86RootPath'], '')
- template: ../steps/run-all-tests-step.yml@self
diff --git a/global.json b/global.json
new file mode 100644
index 0000000000..f923e6de97
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+ "sdk": {
+ "version": "9.0.308",
+ "rollForward": "latestFeature"
+ }
+}
diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
index 56843f73e1..63256d721a 100644
--- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj
@@ -491,8 +491,8 @@
Microsoft\Data\SqlClient\SqlInternalTransaction.cs
-
- Microsoft\Data\SqlClient\SqlMetadataFactory.cs
+
+ Microsoft\Data\SqlClient\SqlMetaDataFactory.cs
Microsoft\Data\SqlClient\SqlNotificationEventArgs.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 de61ff0a54..26b95d1871 100644
--- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
+++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj
@@ -1,4 +1,4 @@
-
+
{407890AC-9876-4FEF-A6F1-F36A876BAADE}
@@ -12,9 +12,6 @@
$(OutputPath)\Microsoft.Data.SqlClient.xml
$(ObjPath)$(AssemblyName)\netfx\
Framework $(BaseProduct)
-
- True
false
$(DefineConstants);NETFRAMEWORK;
@@ -56,7 +53,8 @@
True
True
None
- MinimumRecommendedRules.ruleset
+
+ MinimumRecommendedRules.ruleset
True
True
$(DefineConstants);USEOFFSET;CODE_ANALYSIS_BASELINE;FEATURE_LEGACYSURFACEAREA;FEATURE_UTF32;FEATURE_UTF7;TRACE;
@@ -806,7 +804,7 @@
-
+
@@ -875,17 +873,6 @@
PreserveNewest
-
-
- {5477469E-83B1-11D2-8B49-00A0C9B7C9C4}
- 2
- 4
- 0
- tlbimp
- False
- 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
similarity index 100%
rename from src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetadataFactory.cs
rename to src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/SqlMetaDataFactory.cs
diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
index d788ea079a..83cadc2092 100644
--- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/Microsoft.Data.SqlClient.Tests.csproj
@@ -93,7 +93,6 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
index 02a893eaf5..b2d05cf3f1 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs
@@ -7,16 +7,17 @@
using System.Data;
using System.Data.SqlTypes;
using System.Diagnostics.Tracing;
-using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
+using System.Reflection;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using System.Text;
+using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Azure.Core;
@@ -24,6 +25,7 @@
using Microsoft.Data.SqlClient.TestUtilities;
using Microsoft.Identity.Client;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
@@ -102,7 +104,7 @@ public static bool IsAzureSynapse
{
if (!string.IsNullOrEmpty(TCPConnectionString))
{
- s_sqlServerEngineEdition ??= GetSqlServerProperty(TCPConnectionString, "EngineEdition");
+ s_sqlServerEngineEdition ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.EngineEdition);
}
_ = int.TryParse(s_sqlServerEngineEdition, out int engineEditon);
return engineEditon == 6;
@@ -124,7 +126,7 @@ public static string SQLServerVersion
{
if (!string.IsNullOrEmpty(TCPConnectionString))
{
- s_sQLServerVersion ??= GetSqlServerProperty(TCPConnectionString, "ProductMajorVersion");
+ s_sQLServerVersion ??= GetSqlServerProperty(TCPConnectionString, ServerProperty.ProductMajorVersion);
}
return s_sQLServerVersion;
}
@@ -238,7 +240,9 @@ public static IEnumerable GetConnectionStrings(bool withEnclave)
yield return TCPConnectionString;
}
// Named Pipes are not supported on Unix platform and for Azure DB
- if (Environment.OSVersion.Platform != PlatformID.Unix && IsNotAzureServer() && !string.IsNullOrEmpty(NPConnectionString))
+ if (Environment.OSVersion.Platform != PlatformID.Unix &&
+ IsNotAzureServer() &&
+ !string.IsNullOrEmpty(NPConnectionString))
{
yield return NPConnectionString;
}
@@ -287,29 +291,99 @@ private static Task AcquireTokenAsync(string authorityURL, string userID
public static bool IsKerberosTest => !string.IsNullOrEmpty(KerberosDomainUser) && !string.IsNullOrEmpty(KerberosDomainPassword);
- public static string GetSqlServerProperty(string connectionString, string propertyName)
+ #nullable enable
+
+ ///
+ /// Returns the current test name as:
+ ///
+ /// ClassName.MethodName
+ ///
+ /// xUnit v2 doesn't provide access to a test context, so we use
+ /// reflection into the ITestOutputHelper to get the test name.
+ ///
+ ///
+ ///
+ /// The output helper instance for the currently running test.
+ ///
+ ///
+ /// The current test name.
+ public static string CurrentTestName(ITestOutputHelper outputHelper)
+ {
+ // Reflect our way to the ITest instance.
+ var type = outputHelper.GetType();
+ Assert.NotNull(type);
+ var testMember = type.GetField("test", BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.NotNull(testMember);
+ var test = testMember.GetValue(outputHelper) as ITest;
+ Assert.NotNull(test);
+
+ // The DisplayName is in the format:
+ //
+ // Namespace.ClassName.MethodName(args)
+ //
+ // We only want the ClassName.MethodName portion.
+ //
+ Match match = TestNameRegex.Match(test.DisplayName);
+ Assert.True(match.Success);
+ // There should be 2 groups: the overall match, and the capture
+ // group.
+ Assert.Equal(2, match.Groups.Count);
+
+ // The portion we want is in the capture group.
+ return match.Groups[1].Value;
+ }
+
+ private static readonly Regex TestNameRegex = new(
+ // Capture the ClassName.MethodName portion, which may terminate
+ // the name, or have (args...) appended.
+ @"\.((?:[^.]+)\.(?:[^.\(]+))(?:\(.*\))?$",
+ RegexOptions.Compiled);
+
+ ///
+ /// SQL Server properties we can query.
+ ///
+ /// GOTCHA: The enum member names must match the property names
+ /// queryable via T-SQL SERVERPROPERTY(). See:
+ ///
+ /// https://learn.microsoft.com/en-us/sql/t-sql/functions/serverproperty-transact-sql
+ ///
+ public enum ServerProperty
+ {
+ ProductMajorVersion,
+ EngineEdition
+ }
+
+ public static string GetSqlServerProperty(string connectionString, ServerProperty property)
{
- string propertyValue = string.Empty;
using SqlConnection conn = new(connectionString);
conn.Open();
- SqlCommand command = conn.CreateCommand();
- command.CommandText = $"SELECT SERVERProperty('{propertyName}')";
- SqlDataReader reader = command.ExecuteReader();
- if (reader.Read())
+ return GetSqlServerProperty(conn, property);
+ }
+
+ public static string GetSqlServerProperty(SqlConnection connection, ServerProperty property)
+ {
+ using SqlCommand command = connection.CreateCommand();
+ command.CommandText = $"SELECT SERVERProperty('{property}')";
+ using SqlDataReader reader = command.ExecuteReader();
+
+ Assert.True(reader.Read());
+
+ switch (property)
{
- switch (propertyName)
- {
- case "EngineEdition":
- propertyValue = reader.GetInt32(0).ToString();
- break;
- case "ProductMajorVersion":
- propertyValue = reader.GetString(0);
- break;
- }
+ case ServerProperty.EngineEdition:
+ // EngineEdition is returned as an int.
+ return reader.GetInt32(0).ToString();
+ case ServerProperty.ProductMajorVersion:
+ default:
+ // ProductMajorVersion is returned as a string.
+ //
+ // Assume any unknown property is also a string.
+ return reader.GetString(0);
}
- return propertyValue;
}
+ #nullable disable
+
public static bool GetSQLServerStatusOnTDS8(string connectionString)
{
bool isTDS8Supported = false;
@@ -1168,8 +1242,200 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData)
{
IDs.Add(eventData.EventId);
EventData.Add(eventData);
+ OnMatchingEventWritten(eventData);
}
}
+
+ protected virtual void OnMatchingEventWritten(EventWrittenEventArgs eventData)
+ {
+ }
+ }
+
+ #nullable enable
+
+ public sealed class XEventScope : IDisposable
+ {
+ #region Private Fields
+
+ // Maximum dispatch latency for XEvents, in seconds.
+ private const int MaxDispatchLatencySeconds = 5;
+
+ // The connection to use for all operations.
+ private readonly SqlConnection _connection;
+
+ // True if connected to an Azure SQL instance.
+ private readonly bool _isAzureSql;
+
+ // True if connected to a non-Azure SQL Server 2025 (version 17) or
+ // higher.
+ private readonly bool _isVersion17OrHigher;
+
+ // Duration for the XEvent session, in minutes.
+ private readonly ushort _durationInMinutes;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// The name of the XEvent session, derived from the session name
+ /// provided at construction time, with a unique suffix appended.
+ ///
+ public string SessionName { get; }
+
+ #endregion
+
+ #region Construction
+
+ ///
+ /// Construct with the specified parameters.
+ ///
+ /// This will use the connection to query the server properties and
+ /// setup and start the XEvent session.
+ ///
+ /// The base name of the session.
+ /// The SQL connection to use. (Must already be open.)
+ /// The event specification T-SQL string.
+ /// The target specification T-SQL string.
+ /// The duration of the session in minutes.
+ public XEventScope(
+ string sessionName,
+ // The connection must already be open.
+ SqlConnection connection,
+ string eventSpecification,
+ string targetSpecification,
+ ushort durationInMinutes = 5)
+ {
+ SessionName = GenerateRandomCharacters(sessionName);
+
+ _connection = connection;
+ Assert.Equal(ConnectionState.Open, _connection.State);
+
+ _durationInMinutes = durationInMinutes;
+
+ // EngineEdition 5 indicates Azure SQL.
+ _isAzureSql = GetSqlServerProperty(connection, ServerProperty.EngineEdition) == "5";
+
+ // Determine if we're connected to a SQL Server instance version
+ // 17 or higher.
+ if (!_isAzureSql)
+ {
+ int majorVersion;
+ Assert.True(
+ int.TryParse(
+ GetSqlServerProperty(connection, ServerProperty.ProductMajorVersion),
+ out majorVersion));
+ _isVersion17OrHigher = majorVersion >= 17;
+ }
+
+ // Setup and start the XEvent session.
+ string sessionLocation = _isAzureSql ? "DATABASE" : "SERVER";
+
+ // Both Azure SQL and SQL Server 2025+ support setting a maximum
+ // duration for the XEvent session.
+ string duration =
+ _isAzureSql || _isVersion17OrHigher
+ ? $"MAX_DURATION={_durationInMinutes} MINUTES,"
+ : string.Empty;
+
+ string xEventCreateAndStartCommandText =
+ $@"CREATE EVENT SESSION [{SessionName}] ON {sessionLocation}
+ {eventSpecification}
+ {targetSpecification}
+ WITH (
+ {duration}
+ MAX_MEMORY=16 MB,
+ EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
+ MAX_DISPATCH_LATENCY={MaxDispatchLatencySeconds} SECONDS,
+ MAX_EVENT_SIZE=0 KB,
+ MEMORY_PARTITION_MODE=NONE,
+ TRACK_CAUSALITY=ON,
+ STARTUP_STATE=OFF)
+
+ ALTER EVENT SESSION [{SessionName}] ON {sessionLocation} STATE = START ";
+
+ using SqlCommand createXEventSession = new SqlCommand(xEventCreateAndStartCommandText, _connection);
+ createXEventSession.ExecuteNonQuery();
+ }
+
+ ///
+ /// Disposal stops and drops the XEvent session.
+ ///
+ ///
+ /// Disposal isn't perfect - tests can abort without cleaning up the
+ /// events they have created. For Azure SQL targets that outlive the
+ /// test pipelines, it is beneficial to periodically log into the
+ /// database and drop old XEvent sessions using T-SQL similar to
+ /// this:
+ ///
+ /// DECLARE @sql NVARCHAR(MAX) = N'';
+ ///
+ /// -- Identify inactive (stopped) event sessions and generate DROP commands
+ /// SELECT @sql += N'DROP EVENT SESSION [' + name + N'] ON SERVER;' + CHAR(13) + CHAR(10)
+ /// FROM sys.server_event_sessions
+ /// WHERE running = 0; -- Filter for sessions that are not running (inactive)
+ ///
+ /// -- Print the generated commands for review (optional, but recommended)
+ /// PRINT @sql;
+ ///
+ /// -- Execute the generated commands
+ /// EXEC sys.sp_executesql @sql;
+ ///
+ public void Dispose()
+ {
+ string dropXEventSessionCommand = _isAzureSql
+ // We choose the sys.(database|server)_event_sessions views
+ // here to ensure we find sessions that may not be running.
+ ? $"IF EXISTS (select * from sys.database_event_sessions where name ='{SessionName}')" +
+ $" DROP EVENT SESSION [{SessionName}] ON DATABASE"
+ : $"IF EXISTS (select * from sys.server_event_sessions where name ='{SessionName}')" +
+ $" DROP EVENT SESSION [{SessionName}] ON SERVER";
+
+ using SqlCommand command = new SqlCommand(dropXEventSessionCommand, _connection);
+ command.ExecuteNonQuery();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Query the XEvent session for its collected events, returning
+ /// them as an XML document.
+ ///
+ /// This always blocks the thread for MaxDispatchLatencySeconds to
+ /// ensure that all events have been flushed into the ring buffer.
+ ///
+ public System.Xml.XmlDocument GetEvents()
+ {
+ string xEventQuery = _isAzureSql
+ ? $@"SELECT xet.target_data
+ FROM sys.dm_xe_database_session_targets AS xet
+ INNER JOIN sys.dm_xe_database_sessions AS xe
+ ON (xe.address = xet.event_session_address)
+ WHERE xe.name = '{SessionName}'"
+ : $@"SELECT xet.target_data
+ FROM sys.dm_xe_session_targets AS xet
+ INNER JOIN sys.dm_xe_sessions AS xe
+ ON (xe.address = xet.event_session_address)
+ WHERE xe.name = '{SessionName}'";
+
+ using SqlCommand command = new SqlCommand(xEventQuery, _connection);
+
+ // Wait for maximum dispatch latency to ensure all events
+ // have been flushed to the ring buffer.
+ Thread.Sleep(MaxDispatchLatencySeconds * 1000);
+
+ string? targetData = command.ExecuteScalar() as string;
+ Assert.NotNull(targetData);
+
+ System.Xml.XmlDocument xmlDocument = new System.Xml.XmlDocument();
+
+ xmlDocument.LoadXml(targetData);
+ return xmlDocument;
+ }
+
+ #endregion
}
///
@@ -1198,4 +1464,6 @@ public static string GetMachineFQDN(string hostname)
return fqdn.ToString();
}
}
+
+ #nullable disable
}
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
index e42387f253..e901c019fc 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/Microsoft.Data.SqlClient.ManualTesting.Tests.csproj
@@ -345,7 +345,6 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
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 18de7b7133..506d8f81df 100644
--- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataStreamTest/DataStreamTest.cs
@@ -13,13 +13,21 @@
using System.Threading.Tasks;
using System.Xml;
using Xunit;
+using Xunit.Abstractions;
namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
- public static class DataStreamTest
+ public class DataStreamTest
{
+ private readonly string _testName;
+
+ public DataStreamTest(ITestOutputHelper outputHelper)
+ {
+ _testName = DataTestUtility.CurrentTestName(outputHelper);
+ }
+
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureServer))]
- public static void RunAllTestsForSingleServer_NP()
+ public void RunAllTestsForSingleServer_NP()
{
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
@@ -33,7 +41,7 @@ public static void RunAllTestsForSingleServer_NP()
[ActiveIssue("5540")]
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))]
- public static void RunAllTestsForSingleServer_TCP()
+ public void RunAllTestsForSingleServer_TCP()
{
RunAllTestsForSingleServer(DataTestUtility.TCPConnectionString);
}
@@ -152,7 +160,8 @@ IF OBJECT_ID('dbo.{tableName}', 'U') IS NOT NULL
return data;
}
- private static void RunAllTestsForSingleServer(string connectionString, bool usingNamePipes = false)
+ // @TODO: Split into separate tests!
+ private void RunAllTestsForSingleServer(string connectionString, bool usingNamePipes = false)
{
RowBuffer(connectionString);
InvalidRead(connectionString);
@@ -1911,100 +1920,61 @@ private static void VariantCollationsTest(string connectionString)
}
}
- private static void TestXEventsStreaming(string connectionString)
- {
- string sessionName = DataTestUtility.GenerateRandomCharacters("Session");
+ #nullable enable
- try
- {
- //Create XEvent
- SetupXevent(connectionString, sessionName);
- Task.Factory.StartNew(() =>
- {
- // Read XEvents
- int streamXeventCount = 3;
- using (SqlConnection xEventsReadConnection = new SqlConnection(connectionString))
- {
- xEventsReadConnection.Open();
- string xEventDataStreamCommand = "USE master; " + @"select [type], [data] from sys.fn_MSxe_read_event_stream ('" + sessionName + "',0)";
- using (SqlCommand cmd = new SqlCommand(xEventDataStreamCommand, xEventsReadConnection))
- {
- SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess);
- for (int i = 0; i < streamXeventCount && reader.Read(); i++)
- {
- int colType = reader.GetInt32(0);
- int cb = (int)reader.GetBytes(1, 0, null, 0, 0);
+ private void TestXEventsStreaming(string connectionString)
+ {
+ // Create XEvent
+ using SqlConnection xEventManagementConnection = new SqlConnection(connectionString);
+ xEventManagementConnection.Open();
- byte[] bytes = new byte[cb];
- long read = reader.GetBytes(1, 0, bytes, 0, cb);
+ using DataTestUtility.XEventScope xEventScope =
+ new DataTestUtility.XEventScope(
+ _testName,
+ xEventManagementConnection,
+ "ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))",
+ "ADD TARGET package0.ring_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)
- continue;
+ string sessionName = xEventScope.SessionName;
- using (SqlConnection xEventWriteConnection = new SqlConnection(connectionString))
- {
- xEventWriteConnection.Open();
- string xEventWriteCommandText = @"exec sp_trace_generateevent 90, N'Test2'";
- using (SqlCommand xEventWriteCommand = new SqlCommand(xEventWriteCommandText, xEventWriteConnection))
- {
- xEventWriteCommand.ExecuteNonQuery();
- }
- }
- }
- }
- }
- }).Wait(10000);
- }
- finally
+ Task.Factory.StartNew(() =>
{
- //Delete XEvent
- DeleteXevent(connectionString, sessionName);
- }
- }
+ // Read XEvents
+ int streamXeventCount = 3;
+ using SqlConnection xEventsReadConnection = new SqlConnection(connectionString);
+ xEventsReadConnection.Open();
- private static void SetupXevent(string connectionString, string sessionName)
- {
- string xEventCreateAndStartCommandText = @"CREATE EVENT SESSION [" + sessionName + @"] ON SERVER
- ADD EVENT sqlserver.user_event(ACTION(package0.event_sequence))
- ADD TARGET package0.ring_buffer
- WITH (
- MAX_MEMORY=4096 KB,
- EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,
- MAX_DISPATCH_LATENCY=30 SECONDS,
- MAX_EVENT_SIZE=0 KB,
- MEMORY_PARTITION_MODE=NONE,
- TRACK_CAUSALITY=ON,
- STARTUP_STATE=OFF)
-
- ALTER EVENT SESSION [" + sessionName + "] ON SERVER STATE = START ";
+ string xEventDataStreamCommand = "USE master; " + @"select [type], [data] from sys.fn_MSxe_read_event_stream ('" + sessionName + "',0)";
+ using SqlCommand cmd = new SqlCommand(xEventDataStreamCommand, xEventsReadConnection);
+ using SqlDataReader reader = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess);
- using (SqlConnection connection = new SqlConnection(connectionString))
- {
- connection.Open();
- using (SqlCommand createXeventSession = new SqlCommand(xEventCreateAndStartCommandText, connection))
+ for (int i = 0; i < streamXeventCount && reader.Read(); i++)
{
- createXeventSession.ExecuteNonQuery();
- }
- }
- }
+ int colType = reader.GetInt32(0);
+ int cb = (int)reader.GetBytes(1, 0, null, 0, 0);
- private static void DeleteXevent(string connectionString, string sessionName)
- {
- string deleteXeventSessionCommand = $"IF EXISTS (select * from sys.server_event_sessions where name ='{sessionName}')" +
- $" DROP EVENT SESSION [{sessionName}] ON SERVER";
+ byte[] bytes = new byte[cb];
+ long read = reader.GetBytes(1, 0, bytes, 0, cb);
- using (SqlConnection connection = new SqlConnection(connectionString))
- {
- connection.Open();
- using (SqlCommand deleteXeventSession = new SqlCommand(deleteXeventSessionCommand, connection))
- {
- deleteXeventSession.ExecuteNonQuery();
+ // Don't send data on the first read because there is already data in the buffer.
+ // Don't send data on the last iteration. We will not be reading that data.
+ if (i == 0 || i == streamXeventCount - 1)
+ {
+ continue;
+ }
+
+ using SqlConnection xEventWriteConnection = new SqlConnection(connectionString);
+ xEventWriteConnection.Open();
+
+ string xEventWriteCommandText = @"exec sp_trace_generateevent 90, N'Test2'";
+ using SqlCommand xEventWriteCommand = new SqlCommand(xEventWriteCommandText, xEventWriteConnection);
+ xEventWriteCommand.ExecuteNonQuery();
}
- }
+ }).Wait(10000);
}
+ #nullable disable
+
private static void TimeoutDuringReadAsyncWithClosedReaderTest(string connectionString)
{
// Create the proxy
diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs
new file mode 100644
index 0000000000..bcb23d11bd
--- /dev/null
+++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/TracingTests/XEventsTracingTest.cs
@@ -0,0 +1,94 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using System.Xml.XPath;
+using Xunit;
+using Xunit.Abstractions;
+
+#nullable enable
+
+namespace Microsoft.Data.SqlClient.ManualTesting.Tests
+{
+ public class XEventsTracingTest
+ {
+ private readonly string _testName;
+
+ public XEventsTracingTest(ITestOutputHelper outputHelper)
+ {
+ _testName = DataTestUtility.CurrentTestName(outputHelper);
+ }
+
+ [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsNotManagedInstance))]
+ [InlineData("SELECT @@VERSION", System.Data.CommandType.Text, "sql_statement_starting")]
+ [InlineData("sp_help", System.Data.CommandType.StoredProcedure, "rpc_starting")]
+ public void XEventActivityIDConsistentWithTracing(string query, System.Data.CommandType commandType, string xEvent)
+ {
+ // This test validates that the activity ID recorded in the client-side trace is passed through to the server,
+ // where it can be recorded in an XEvent session. This is documented at:
+ // https://learn.microsoft.com/en-us/sql/relational-databases/native-client/features/accessing-diagnostic-information-in-the-extended-events-log
+
+ using SqlConnection activityConnection = new(DataTestUtility.TCPConnectionString);
+ activityConnection.Open();
+
+ Guid connectionId = activityConnection.ClientConnectionId;
+ HashSet ids;
+
+ using SqlConnection xEventManagementConnection = new(DataTestUtility.TCPConnectionString);
+ xEventManagementConnection.Open();
+
+ using DataTestUtility.XEventScope xEventSession = new(
+ _testName,
+ xEventManagementConnection,
+ $@"ADD EVENT SQL_STATEMENT_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}')),
+ ADD EVENT RPC_STARTING (ACTION (client_connection_id) WHERE (client_connection_id='{connectionId}'))",
+ "ADD TARGET ring_buffer");
+
+ using (DataTestUtility.MDSEventListener TraceListener = new())
+ {
+ using SqlCommand command = new(query, activityConnection) { CommandType = commandType };
+ using SqlDataReader reader = command.ExecuteReader();
+ while (reader.Read())
+ {
+ // Flush data
+ }
+
+ ids = TraceListener.ActivityIDs;
+ }
+
+ XmlDocument eventList = xEventSession.GetEvents();
+ // Get the associated activity ID from the XEvent session. We expect to see the same ID in the trace as well.
+ string activityId = GetCommandActivityId(query, xEvent, connectionId, eventList);
+
+ Assert.Contains(activityId, ids);
+ }
+
+ private static string GetCommandActivityId(string commandText, string eventName, Guid connectionId, XmlDocument xEvents)
+ {
+ XPathNavigator? xPathRoot = xEvents.CreateNavigator();
+ 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}')]");
+
+ 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");
+
+ Assert.NotNull(activityIdElement);
+ Assert.NotNull(activityIdElement.Value);
+
+ return activityIdElement.Value;
+ }
+ }
+}
diff --git a/tools/props/Versions.props b/tools/props/Versions.props
index 09398b9e6f..03e4ff61f1 100644
--- a/tools/props/Versions.props
+++ b/tools/props/Versions.props
@@ -54,25 +54,24 @@
0.13.2
3.1.6
- 10.0.0-beta.24564.1
- 8.0.0-beta.24123.1
- 6.0.1
- 2.0.8
+ 10.0.0-beta.25164.6
+ 10.0.0-beta.25164.6
+ 8.0.1
1.0.3
- 17.8.0
+ 17.11.1
172.52.0
10.50.1600.1
160.1000.6
5.0.0
- 13.0.1
- 6.0.1
+ 13.0.3
+ 8.0.1
6.0.1
4.3.0
5.0.0
5.0.0
- 6.0.0
+ 8.0.1
6.0.0
- 2.9.3
+ 2.9.2
2.8.2