diff --git a/BUILDGUIDE.md b/BUILDGUIDE.md index 1ebb710197..1943ef0bbc 100644 --- a/BUILDGUIDE.md +++ b/BUILDGUIDE.md @@ -119,7 +119,7 @@ Manual Tests require the below setup to run: |AzureKeyVaultClientSecret | (Optional) "Client Secret" of the Active Directory registered application, granted access to the Azure Key Vault specified in `AZURE_KEY_VAULT_URL` | _{Client Application Secret}_ | |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.| |SupportsIntegratedSecurity | (Optional) Whether or not the USER running tests has integrated security access to the target SQL Server.| `true` OR `false`| - |SupportsFileStream | (Optional) Whether or not FileStream is enabled on SQL Server| `true` OR `false`| + |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`| |IsAzureSynpase | (Optional) When set to 'true', test suite runs compatible tests for Azure Synapse/Parallel Data Warehouse. | `true` OR `false`| diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs deleted file mode 100644 index c925335791..0000000000 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.NetCoreApp.cs +++ /dev/null @@ -1,60 +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. -// ------------------------------------------------------------------------------ -// Changes to this file must follow the http://aka.ms/api-review process. -// ------------------------------------------------------------------------------ - -namespace Microsoft.Data.SqlTypes -{ - /// - public sealed partial class SqlFileStream : System.IO.Stream - { - /// - public SqlFileStream(string path, byte[] transactionContext, System.IO.FileAccess access) { } - /// - public SqlFileStream(string path, byte[] transactionContext, System.IO.FileAccess access, System.IO.FileOptions options, System.Int64 allocationSize) { } - /// - public string Name { get { throw null; } } - /// - public byte[] TransactionContext { get { throw null; } } - /// - public override bool CanRead { get { throw null; } } - /// - public override bool CanSeek { get { throw null; } } - /// - public override bool CanTimeout { get { throw null; } } - /// - public override bool CanWrite { get { throw null; } } - /// - public override long Length { get { throw null; } } - /// - public override long Position { get { throw null; } set { throw null; } } - /// - public override int ReadTimeout { get { throw null; } } - /// - public override int WriteTimeout { get { throw null; } } - /// - public override void Flush() { } - /// - public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } - /// - public override int EndRead(System.IAsyncResult asyncResult) { throw null; } - /// - public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, System.Object state) { throw null; } - /// - public override void EndWrite(System.IAsyncResult asyncResult) { } - /// - public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } - /// - public override void SetLength(long value) { throw null; } - /// - public override int Read(byte[] buffer, int offset, int count) { throw null; } - /// - public override int ReadByte() { throw null; } - /// - public override void Write(byte[] buffer, int offset, int count) { throw null; } - /// - public override void WriteByte(byte value) { } - } -} diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs index 659f490053..fbcfa45e9d 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.cs @@ -28,6 +28,59 @@ public SqlNotificationRequest(string userData, string options, int timeout) { } public string UserData { get { throw null; } set { } } } } +namespace Microsoft.Data.SqlTypes +{ + /// + public sealed partial class SqlFileStream : System.IO.Stream + { + /// + public SqlFileStream(string path, byte[] transactionContext, System.IO.FileAccess access) { } + /// + public SqlFileStream(string path, byte[] transactionContext, System.IO.FileAccess access, System.IO.FileOptions options, System.Int64 allocationSize) { } + /// + public string Name { get { throw null; } } + /// + public byte[] TransactionContext { get { throw null; } } + /// + public override bool CanRead { get { throw null; } } + /// + public override bool CanSeek { get { throw null; } } + /// + public override bool CanTimeout { get { throw null; } } + /// + public override bool CanWrite { get { throw null; } } + /// + public override long Length { get { throw null; } } + /// + public override long Position { get { throw null; } set { throw null; } } + /// + public override int ReadTimeout { get { throw null; } } + /// + public override int WriteTimeout { get { throw null; } } + /// + public override void Flush() { } + /// + public override System.IAsyncResult BeginRead(byte[] buffer, int offset, int count, System.AsyncCallback callback, object state) { throw null; } + /// + public override int EndRead(System.IAsyncResult asyncResult) { throw null; } + /// + public override System.IAsyncResult BeginWrite(byte[] buffer, int offset, int count, System.AsyncCallback callback, System.Object state) { throw null; } + /// + public override void EndWrite(System.IAsyncResult asyncResult) { } + /// + public override long Seek(long offset, System.IO.SeekOrigin origin) { throw null; } + /// + public override void SetLength(long value) { throw null; } + /// + public override int Read(byte[] buffer, int offset, int count) { throw null; } + /// + public override int ReadByte() { throw null; } + /// + public override void Write(byte[] buffer, int offset, int count) { throw null; } + /// + public override void WriteByte(byte value) { } + } +} namespace Microsoft.Data.SqlClient { /// diff --git a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj index 3d70bf7dce..c56cf83540 100644 --- a/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/ref/Microsoft.Data.SqlClient.csproj @@ -23,7 +23,6 @@ - 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 f42f2ecd05..4099202001 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -575,12 +575,12 @@ - - + + - - + + Common\CoreLib\Interop\Windows\kernel32\Interop.FileTypes.cs diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlFileStream.Windows.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlFileStream.Windows.cs index d52bfadf30..017320168b 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlFileStream.Windows.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlTypes/SqlFileStream.Windows.cs @@ -689,8 +689,13 @@ static private string InitializeNtPath(string path) // Ensure we have validated and normalized the path before AssertPathFormat(path); string uniqueId = Guid.NewGuid().ToString("N"); - return System.IO.PathInternal.IsDeviceUNC(path) ? string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", path.Replace(@"\\.", @"\??"), uniqueId) - : string.Format(CultureInfo.InvariantCulture, @"\??\UNC\{0}\{1}", path.Trim('\\'), uniqueId); +#if NETSTANDARD + return System.IO.PathInternal.IsDeviceUNC(path.AsSpan()) +#else + return System.IO.PathInternal.IsDeviceUNC(path) +#endif + ? string.Format(CultureInfo.InvariantCulture, @"{0}\{1}", path.Replace(@"\\.", @"\??"), uniqueId) + : string.Format(CultureInfo.InvariantCulture, @"\??\UNC\{0}\{1}", path.Trim('\\'), uniqueId); } } } 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 645c11928e..b74f39f60c 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 @@ -8,6 +8,7 @@ $(DefineConstants);NETFX $(DefineConstants);NETCOREAPP $(DefineConstants);NET50_OR_LATER + NETSTANDARDREFERNCE $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs index 79b8c08df0..df97e95fcd 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlCommandTest.cs @@ -125,7 +125,7 @@ public void Constructor3() Assert.Null(cmd.Container); Assert.True(cmd.DesignTimeVisible); Assert.Null(cmd.Notification); -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif @@ -166,7 +166,7 @@ public void Constructor4() Assert.Null(cmd.Container); Assert.True(cmd.DesignTimeVisible); Assert.Null(cmd.Notification); -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif @@ -184,7 +184,7 @@ public void Constructor4() Assert.Null(cmd.Container); Assert.True(cmd.DesignTimeVisible); Assert.Null(cmd.Notification); -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif @@ -202,7 +202,7 @@ public void Constructor4() Assert.Null(cmd.Container); Assert.True(cmd.DesignTimeVisible); Assert.Null(cmd.Notification); -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif @@ -224,7 +224,7 @@ public void Clone() cmd.CommandType = CommandType.StoredProcedure; cmd.DesignTimeVisible = false; cmd.Notification = notificationReq; -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif @@ -240,7 +240,7 @@ public void Clone() Assert.Null(cmd.Connection); Assert.False(cmd.DesignTimeVisible); Assert.Same(notificationReq, cmd.Notification); -#if NETFX +#if NETFX && !NETSTANDARDREFERNCE // see https://github.com/dotnet/SqlClient/issues/17 Assert.True(cmd.NotificationAutoEnlist); #endif diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs index 8ea16c9a83..b60b10e5d8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs @@ -44,10 +44,10 @@ public static class DataTestUtility public static bool EnclaveEnabled { get; private set; } = false; public static readonly bool TracingEnabled = false; public static readonly bool SupportsIntegratedSecurity = false; - public static readonly bool SupportsFileStream = false; public static readonly bool UseManagedSNIOnWindows = false; public static readonly bool IsAzureSynapse = false; public static Uri AKVBaseUri = null; + public static string FileStreamDirectory = null; public static readonly string DNSCachingConnString = null; public static readonly string DNSCachingServerCR = null; // this is for the control ring @@ -85,7 +85,7 @@ static DataTestUtility() AADServicePrincipalSecret = c.AADServicePrincipalSecret; LocalDbAppName = c.LocalDbAppName; SupportsIntegratedSecurity = c.SupportsIntegratedSecurity; - SupportsFileStream = c.SupportsFileStream; + FileStreamDirectory = c.FileStreamDirectory; EnclaveEnabled = c.EnclaveEnabled; TracingEnabled = c.TracingEnabled; UseManagedSNIOnWindows = c.UseManagedSNIOnWindows; @@ -437,10 +437,8 @@ public static void DropStoredProcedure(SqlConnection sqlConnection, string spNam /// Database name without brackets. public static void DropDatabase(SqlConnection sqlConnection, string dbName) { - using (SqlCommand cmd = new SqlCommand(string.Format("IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{0}')) \nBEGIN \n ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE \n DROP DATABASE [{0}] \nEND", dbName), sqlConnection)) - { - cmd.ExecuteNonQuery(); - } + using SqlCommand cmd = new(string.Format("IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{0}')) \nBEGIN \n ALTER DATABASE [{0}] SET SINGLE_USER WITH ROLLBACK IMMEDIATE \n DROP DATABASE [{0}] \nEND", dbName), sqlConnection); + cmd.ExecuteNonQuery(); } public static bool IsLocalDBInstalled() => !string.IsNullOrEmpty(LocalDbAppName?.Trim()); @@ -492,7 +490,7 @@ public static string GetUserIdentityAccessToken() public static bool IsUserIdentityTokenSetup() => !string.IsNullOrEmpty(GetUserIdentityAccessToken()); - public static bool IsFileStreamSetup() => SupportsFileStream; + public static bool IsFileStreamSetup() => !string.IsNullOrEmpty(FileStreamDirectory); private static bool CheckException(Exception ex, string exceptionMessage, bool innerExceptionMustBeNull) where TException : Exception { 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 a308ef7520..5f871d93b4 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 @@ -200,6 +200,7 @@ + @@ -261,9 +262,6 @@ - - - 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 24dde457e5..1bf5bde2a1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlFileStreamTest/SqlFileStreamTest.cs @@ -16,53 +16,68 @@ public static class SqlFileStreamTest private static bool AreConnectionStringsSetup() => DataTestUtility.AreConnStringsSetup(); private static bool IsIntegratedSecurityEnvironmentSet() => DataTestUtility.IsIntegratedSecuritySetup(); - private static int[] s_insertedValues = { 11 , 22 }; + private static int[] s_insertedValues = { 11, 22 }; + private static string s_fileStreamDBName = null; [PlatformSpecific(TestPlatforms.Windows)] [ConditionalFact(nameof(IsFileStreamEnvironmentSet), nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] public static void ReadFilestream() { - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + string connString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + InitialCatalog = SetupFileStreamDB(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString) + }.ConnectionString; + + using SqlConnection connection = new(connString); connection.Open(); string tempTable = SetupTable(connection); int nRow = 0; byte[] retrievedValue; - SqlCommand command = new SqlCommand($"SELECT Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); - - SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - command.Transaction = transaction; - - using (SqlDataReader reader = command.ExecuteReader()) + SqlCommand command = new($"SELECT Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); + try { - while (reader.Read()) + SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); + command.Transaction = transaction; + + using (SqlDataReader reader = command.ExecuteReader()) { - // Get the pointer for the file. - string path = reader.GetString(0); - byte[] transactionContext = reader.GetSqlBytes(1).Buffer; - - // Create the SqlFileStream - using (Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.Read, FileOptions.SequentialScan, allocationSize: 0)) + while (reader.Read()) { - // Read the contents as bytes. - retrievedValue = new byte[fileStream.Length]; - fileStream.Read(retrievedValue,0,(int)(fileStream.Length)); - - // Reverse the byte array, if the system architecture is little-endian. - if (BitConverter.IsLittleEndian) - Array.Reverse(retrievedValue); - - // Compare inserted and retrieved values. - Assert.Equal(s_insertedValues[nRow], BitConverter.ToInt32(retrievedValue,0)); + // Get the pointer for the file. + string path = reader.GetString(0); + byte[] transactionContext = reader.GetSqlBytes(1).Buffer; + + // Create the SqlFileStream + using (Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.Read, FileOptions.SequentialScan, allocationSize: 0)) + { + // Read the contents as bytes. + retrievedValue = new byte[fileStream.Length]; + fileStream.Read(retrievedValue, 0, (int)(fileStream.Length)); + + // Reverse the byte array, if the system architecture is little-endian. + if (BitConverter.IsLittleEndian) + Array.Reverse(retrievedValue); + + // Compare inserted and retrieved values. + Assert.Equal(s_insertedValues[nRow], BitConverter.ToInt32(retrievedValue, 0)); + } + nRow++; } - nRow++; + } - + transaction.Commit(); + } + finally + { + // Drop Table + ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); } - transaction.Commit(); - - // Drop Table - ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); + } + finally + { + DropFileStreamDb(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString); } } @@ -70,8 +85,14 @@ public static void ReadFilestream() [ConditionalFact(nameof(IsFileStreamEnvironmentSet), nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] public static void OverwriteFilestream() { - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + string connString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + InitialCatalog = SetupFileStreamDB(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString) + }.ConnectionString; + + using SqlConnection connection = new(connString); connection.Open(); string tempTable = SetupTable(connection); byte[] insertedValue = BitConverter.GetBytes(3); @@ -79,36 +100,42 @@ public static void OverwriteFilestream() // Reverse the byte array, if the system architecture is little-endian. if (BitConverter.IsLittleEndian) Array.Reverse(insertedValue); + try + { + SqlCommand command = new($"SELECT TOP(1) Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); - SqlCommand command = new SqlCommand($"SELECT TOP(1) Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); - - SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - command.Transaction = transaction; + SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); + command.Transaction = transaction; - using (SqlDataReader reader = command.ExecuteReader()) - { - while (reader.Read()) + using (SqlDataReader reader = command.ExecuteReader()) { - // Get the pointer for file - string path = reader.GetString(0); - byte[] transactionContext = reader.GetSqlBytes(1).Buffer; - - // Create the SqlFileStream - using (Stream fileStream = new SqlFileStream(path, transactionContext, FileAccess.Write, FileOptions.SequentialScan, allocationSize: 0)) + while (reader.Read()) { + // Get the pointer for file + string path = reader.GetString(0); + byte[] transactionContext = reader.GetSqlBytes(1).Buffer; + + // 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); } } + transaction.Commit(); + + // Compare inserted and retrieved value + byte[] retrievedValue = RetrieveData(tempTable, connection, insertedValue.Length); + Assert.Equal(insertedValue, retrievedValue); + } + finally + { + // Drop Table + ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); } - transaction.Commit(); - - // Compare inserted and retrieved value - byte[] retrievedValue = RetrieveData(tempTable, connection, insertedValue.Length); - Assert.Equal(insertedValue, retrievedValue); - - // Drop Table - ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); + } + finally + { + DropFileStreamDb(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString); } } @@ -116,8 +143,14 @@ public static void OverwriteFilestream() [ConditionalFact(nameof(IsFileStreamEnvironmentSet), nameof(IsIntegratedSecurityEnvironmentSet), nameof(AreConnectionStringsSetup))] public static void AppendFilestream() { - using (SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString)) + try { + string connString = new SqlConnectionStringBuilder(DataTestUtility.TCPConnectionString) + { + InitialCatalog = SetupFileStreamDB(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString) + }.ConnectionString; + + using SqlConnection connection = new(connString); connection.Open(); string tempTable = SetupTable(connection); @@ -129,21 +162,22 @@ public static void AppendFilestream() if (BitConverter.IsLittleEndian) Array.Reverse(insertedValue); - SqlCommand command = new SqlCommand($"SELECT TOP(1) Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); + try + { + SqlCommand command = new($"SELECT TOP(1) Photo.PathName(), GET_FILESTREAM_TRANSACTION_CONTEXT(),EmployeeId FROM {tempTable} ORDER BY EmployeeId", connection); - SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); - command.Transaction = transaction; + SqlTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadCommitted); + command.Transaction = transaction; - using (SqlDataReader reader = command.ExecuteReader()) - { - while (reader.Read()) + using (SqlDataReader reader = command.ExecuteReader()) { - // 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)) + while (reader.Read()) { + // 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 fileStream.Seek(0, SeekOrigin.End); @@ -151,18 +185,81 @@ public static void AppendFilestream() fileStream.WriteByte(appendedByte); } } - } - transaction.Commit(); + transaction.Commit(); - // Compare inserted and retrieved value - byte[] retrievedValue = RetrieveData(tempTable, connection, insertedValue.Length); - Assert.Equal(insertedValue, retrievedValue); + // Compare inserted and retrieved value + byte[] retrievedValue = RetrieveData(tempTable, connection, insertedValue.Length); + Assert.Equal(insertedValue, retrievedValue); - // Drop Table - ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); + } + finally + { + // Drop Table + ExecuteNonQueryCommand($"DROP TABLE {tempTable}", connection); + } + } + finally + { + DropFileStreamDb(ref DataTestUtility.FileStreamDirectory, DataTestUtility.TCPConnectionString); } } + #region Private helper methods + + private static string SetupFileStreamDB(ref string fileStreamDir, string connString) + { + try + { + if (fileStreamDir != null) + { + if (!fileStreamDir.EndsWith("\\")) + { + fileStreamDir += "\\"; + } + + string dbName = DataTestUtility.GetUniqueName("FS", false); + string createDBQuery = @$"CREATE DATABASE [{dbName}] + ON PRIMARY + (NAME = PhotoLibrary_data, + FILENAME = '{fileStreamDir}PhotoLibrary_data.mdf'), + FILEGROUP FileStreamGroup CONTAINS FILESTREAM + (NAME = PhotoLibrary_blobs, + FILENAME = '{fileStreamDir}Photos') + LOG ON + (NAME = PhotoLibrary_log, + FILENAME = '{fileStreamDir}PhotoLibrary_log.ldf')"; + using SqlConnection con = new(new SqlConnectionStringBuilder(connString) { InitialCatalog = "master" }.ConnectionString); + con.Open(); + using SqlCommand cmd = con.CreateCommand(); + cmd.CommandText = createDBQuery; + cmd.ExecuteNonQuery(); + s_fileStreamDBName = dbName; + } + } + catch (SqlException e) + { + Console.WriteLine("File Stream database could not be setup. " + e.Message); + fileStreamDir = null; + } + return s_fileStreamDBName; + } + + private static void DropFileStreamDb(ref string fileStreamDir, string connString) + { + try + { + using SqlConnection con = new(new SqlConnectionStringBuilder(connString) { InitialCatalog = "master" }.ConnectionString); + con.Open(); + DataTestUtility.DropDatabase(con, s_fileStreamDBName); + s_fileStreamDBName = null; + } + catch (SqlException e) + { + Console.WriteLine("File Stream database could not be dropped. " + e.Message); + fileStreamDir = null; + } + } + private static string SetupTable(SqlConnection conn) { // Generate random table name @@ -184,16 +281,14 @@ private static string SetupTable(SqlConnection conn) private static void ExecuteNonQueryCommand(string cmdText, SqlConnection conn) { - using (SqlCommand cmd = conn.CreateCommand()) - { - cmd.CommandText = cmdText; - cmd.ExecuteNonQuery(); - } + using SqlCommand cmd = conn.CreateCommand(); + cmd.CommandText = cmdText; + cmd.ExecuteNonQuery(); } private static byte[] RetrieveData(string tempTable, SqlConnection conn, int len) { - SqlCommand command = new SqlCommand($"SELECT TOP(1) Photo FROM {tempTable}", conn); + SqlCommand command = new($"SELECT TOP(1) Photo FROM {tempTable}", conn); byte[] bArray = new byte[len]; using (SqlDataReader reader = command.ExecuteReader()) { 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 c5a3da8c3e..ce1aaaca86 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 @@ -27,7 +27,7 @@ public class Config public bool EnclaveEnabled = false; public bool TracingEnabled = false; public bool SupportsIntegratedSecurity = false; - public bool SupportsFileStream = false; + public string FileStreamDirectory = null; public bool UseManagedSNIOnWindows = false; public string DNSCachingConnString = null; public string DNSCachingServerCR = null; // this is for the control ring 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 8d50730a33..6b4e45ef8f 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 @@ -16,7 +16,7 @@ "AzureKeyVaultClientSecret": "", "SupportsIntegratedSecurity": true, "LocalDbAppName": "", - "SupportsFileStream": false, + "FileStreamDirectory": "", "UseManagedSNIOnWindows": false, "DNSCachingConnString": "", "DNSCachingServerCR": "",