diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs index b2232b3f69..8feb0e38f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/AzureKeyVaultKeyFixtureBase.cs @@ -75,18 +75,38 @@ public void Dispose() protected virtual void Dispose(bool disposing) { - foreach (KeyVaultKey key in _createdKeys) + try { - try - { - _keyClient.StartDeleteKey(key.Name).WaitForCompletion(); - } - catch (Exception) + foreach (KeyVaultKey key in _createdKeys) { - continue; + try + { + _keyClient.StartDeleteKey(key.Name).WaitForCompletion(); + } + catch (Exception) + { + continue; + } + + // A deleted key remains in a soft-deleted state (and continues to consume the name) + // until it is purged or the retention period expires. Purging is best-effort: the + // test principal may not have permission, and the vault may have purge protection + // enabled, in which case the soft-deleted key simply expires on its own. + try + { + _keyClient.PurgeDeletedKey(key.Name); + } + catch (Exception) + { + continue; + } } - } - _randomGenerator.Dispose(); + _createdKeys.Clear(); + } + finally + { + _randomGenerator.Dispose(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs index 5081bc8b18..88b3a2c7b4 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs @@ -50,7 +50,23 @@ public CertificateStoreContext(StoreLocation location, StoreName name) private readonly List _certificateStoreModifications = new List(); + /// + /// Every certificate handed out by . Certificates are created with + /// , which writes the private key to a key container on + /// disk. Removing the certificate from a store does not remove that container, so the containers must + /// be deleted explicitly or they accumulate indefinitely across test runs. + /// + private readonly List _createdCertificates = new List(); + protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses, bool forceCsp = false) + { + X509Certificate2 certificate = CreateCertificateCore(subjectName, dnsNames, ipAddresses, forceCsp); + + _createdCertificates.Add(certificate); + return certificate; + } + + private X509Certificate2 CreateCertificateCore(string subjectName, IEnumerable dnsNames, IEnumerable ipAddresses, bool forceCsp = false) { // This will always generate a certificate with: // * Start date: 24hrs ago @@ -281,21 +297,45 @@ public void Dispose() protected virtual void Dispose(bool disposing) { + // Collect everything that needs disposing before touching any of it: removal from a store and + // deletion of the persisted key are both best-effort, but the handles must always be released. + List certificates = new List(_createdCertificates); + + // Remove the certificates from any store they were added to. This must happen before the + // certificates are disposed, because a disposed certificate cannot be matched against a store. foreach (CertificateStoreContext storeContext in _certificateStoreModifications) { using X509Store store = new X509Store(storeContext.Name, storeContext.Location); + bool opened; try { store.Open(OpenFlags.ReadWrite); + opened = true; } - catch (Exception) + catch (Exception ex) { - continue; + // Every certificate destined for this store is about to be skipped, so this is the + // only chance to say so. Without it the certificates leak across runs with no trace. + Console.WriteLine( + $"Failed to open certificate store '{storeContext.Name}' in '{storeContext.Location}' " + + $"for cleanup; {storeContext.Certificates.Count} certificate(s) may be left behind. {ex}"); + + opened = false; } foreach (X509Certificate2 cert in storeContext.Certificates) { + if (!certificates.Contains(cert)) + { + certificates.Add(cert); + } + + if (!opened) + { + continue; + } + try { if (store.Certificates.Contains(cert)) @@ -303,15 +343,75 @@ protected virtual void Dispose(bool disposing) store.Remove(cert); } } - catch (Exception) + catch (Exception ex) { + // Same reasoning as the store-open failure above: report the leak rather than + // letting the certificate linger in the store silently. + Console.WriteLine( + $"Failed to remove certificate '{cert.Subject}' from store '{storeContext.Name}' " + + $"in '{storeContext.Location}'; it may be left behind. {ex}"); + continue; } + } + + storeContext.Certificates.Clear(); + } + + _certificateStoreModifications.Clear(); + foreach (X509Certificate2 cert in certificates) + { + DeletePersistedPrivateKey(cert); + + try + { cert.Dispose(); } + catch (Exception) + { + // Nothing further can be done about a certificate that refuses to release its handle. + } + } - storeContext.Certificates.Clear(); + _createdCertificates.Clear(); + } + + /// + /// Deletes the on-disk key container backing a certificate's private key, if there is one. + /// + /// + /// This is best-effort: the certificate may have no private key, the key may be ephemeral, or the + /// current user may lack permission to delete it (for example, for a machine-scoped key). + /// + private static void DeletePersistedPrivateKey(X509Certificate2 certificate) + { + try + { + if (!certificate.HasPrivateKey) + { + return; + } + + using RSA? privateKey = certificate.GetRSAPrivateKey(); + + switch (privateKey) + { + case RSACryptoServiceProvider csp: + csp.PersistKeyInCsp = false; + break; +#if NET + case RSACng cng: + cng.Key.Delete(); + break; +#endif + default: + break; + } + } + catch (Exception) + { + // Best-effort; a stale key container is preferable to failing the test run during cleanup. } } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs index c5b527c690..ff2dc0719b 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs @@ -28,29 +28,41 @@ public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase public ColumnEncryptionCertificateFixture() { - PrimaryColumnEncryptionCertificate = CreateCertificate(nameof(PrimaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); - SecondaryColumnEncryptionCertificate = CreateCertificate(nameof(SecondaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); - _currentUserCertificate = CreateCertificate(nameof(_currentUserCertificate), Array.Empty(), Array.Empty()); - using (X509Certificate2 createdCertificate = CreateCertificate(nameof(CertificateWithoutPrivateKey), Array.Empty(), Array.Empty())) + // NOTE: If this constructor throws, xUnit never calls Dispose, so any certificate already + // placed in a store (and its persisted key container) would be leaked. + try { + PrimaryColumnEncryptionCertificate = CreateCertificate(nameof(PrimaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); + SecondaryColumnEncryptionCertificate = CreateCertificate(nameof(SecondaryColumnEncryptionCertificate), Array.Empty(), Array.Empty()); + _currentUserCertificate = CreateCertificate(nameof(_currentUserCertificate), Array.Empty(), Array.Empty()); + + // NOTE: The source certificate is intentionally not disposed here; it is tracked by the base + // fixture, which deletes its persisted key container and disposes it on cleanup. + X509Certificate2 createdCertificate = CreateCertificate(nameof(CertificateWithoutPrivateKey), Array.Empty(), Array.Empty()); + // This will strip the private key away from the created certificate #if NET9_0_OR_GREATER - CertificateWithoutPrivateKey = X509CertificateLoader.LoadCertificate(createdCertificate.Export(X509ContentType.Cert)); + CertificateWithoutPrivateKey = X509CertificateLoader.LoadCertificate(createdCertificate.Export(X509ContentType.Cert)); #else CertificateWithoutPrivateKey = new X509Certificate2(createdCertificate.Export(X509ContentType.Cert)); #endif AddToStore(CertificateWithoutPrivateKey, StoreLocation.CurrentUser, StoreName.My); - } - AddToStore(PrimaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); - AddToStore(SecondaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); - AddToStore(_currentUserCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(PrimaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(SecondaryColumnEncryptionCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(_currentUserCertificate, StoreLocation.CurrentUser, StoreName.My); - if (IsAdmin) - { - _localMachineCertificate = CreateCertificate(nameof(_localMachineCertificate), Array.Empty(), Array.Empty()); + if (IsAdmin) + { + _localMachineCertificate = CreateCertificate(nameof(_localMachineCertificate), Array.Empty(), Array.Empty()); - AddToStore(_localMachineCertificate, StoreLocation.LocalMachine, StoreName.My); + AddToStore(_localMachineCertificate, StoreLocation.LocalMachine, StoreName.My); + } + } + catch + { + Dispose(); + throw; } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs index d396251103..5cb70cc27a 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -28,11 +28,21 @@ protected ColumnMasterKeyCertificateFixture(bool createCertificate) { if (createCertificate) { - ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); - - AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); - - ColumnMasterKeyCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}"; + // NOTE: If this constructor throws, xUnit never calls Dispose, so the certificate placed in + // the store (and its persisted key container) would be leaked. + try + { + ColumnMasterKeyCertificate = CreateCertificate(nameof(ColumnMasterKeyCertificate), Array.Empty(), Array.Empty()); + + AddToStore(ColumnMasterKeyCertificate, StoreLocation.CurrentUser, StoreName.My); + + ColumnMasterKeyCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{ColumnMasterKeyCertificate.Thumbprint}"; + } + catch + { + Dispose(); + throw; + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs index 4d46032ce2..ac8e847656 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs @@ -19,12 +19,22 @@ public class CspCertificateFixture : CertificateFixtureBase { public CspCertificateFixture() { - CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true); + // NOTE: If this constructor throws, xUnit never calls Dispose, so the certificate placed in the + // store (and its persisted CSP key container) would be leaked. + try + { + CspCertificate = CreateCertificate(nameof(CspCertificate), Array.Empty(), Array.Empty(), true); - AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My); + AddToStore(CspCertificate, StoreLocation.CurrentUser, StoreName.My); - CspCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{CspCertificate.Thumbprint}"; - CspKeyPath = GetCspPathFromCertificate(); + CspCertificatePath = $"{StoreLocation.CurrentUser}/{StoreName.My}/{CspCertificate.Thumbprint}"; + CspKeyPath = GetCspPathFromCertificate(); + } + catch + { + Dispose(); + throw; + } } public X509Certificate2 CspCertificate { get; } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnEncryptionKey.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnEncryptionKey.cs index af402428b0..bdad3fbdd5 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnEncryptionKey.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnEncryptionKey.cs @@ -27,7 +27,7 @@ public sealed class ColumnEncryptionKey : DatabaseObject /// The column master key which backs this encryption key. public ColumnEncryptionKey(SqlConnection connection, string namePrefix, ColumnMasterKey cmkOrigin) : base(connection, GenerateLongName(namePrefix), definition: DefinitionTemplate, - state: cmkOrigin, shouldCreate: true, shouldDrop: true) + state: cmkOrigin) { } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnMasterKey.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnMasterKey.cs index de607c0f68..0802cbbe53 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnMasterKey.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ColumnMasterKey.cs @@ -37,7 +37,7 @@ internal CreationParameters(SqlColumnEncryptionKeyStoreProvider provider, protected ColumnMasterKey(SqlConnection connection, string namePrefix, CreationParameters creationParameters) : base(connection, name: GenerateLongName(namePrefix), definition: DefinitionTemplate, - state: creationParameters, shouldCreate: true, shouldDrop: true) + state: creationParameters) { } 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 a74da79697..a93df61fca 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseObject.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Text; namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; @@ -16,7 +17,7 @@ namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; /// public abstract class DatabaseObject : IDisposable { - private readonly bool _shouldDrop; + private bool _disposed; protected SqlConnection Connection { get; } @@ -26,20 +27,51 @@ public abstract class DatabaseObject : IDisposable public string UnescapedName => Name.Substring(1, Name.Length - 2).Replace("]]", "]"); - protected DatabaseObject(SqlConnection connection, string name, string definition, TState state, bool shouldCreate, bool shouldDrop) + protected DatabaseObject(SqlConnection connection, string name, string definition, TState state) { - _shouldDrop = shouldDrop; - Connection = connection; State = state; Name = name; - if (shouldCreate) + EnsureConnectionOpen(); + + // Remove any object left behind by an earlier run before creating this one. Best-effort: + // if it cannot be removed, CREATE will report the collision far more clearly than a drop + // failure would. + TryDrop(); + + try { - EnsureConnectionOpen(); - DropObject(); CreateObject(definition); } + catch + { + // CREATE can fail *after* the server has created the object: a command timeout or a + // dropped connection reports failure for a statement that may already have committed. + // Every generated name embeds a GUID, so anything left behind is orphaned forever in + // the (shared) test database. Unwind through Dispose so that cleanup lives in exactly + // one place, then let the original failure surface. + Dispose(); + throw; + } + } + + /// + /// Adopts an object that already exists, so that it is dropped when this instance is disposed. + /// Nothing is created. + /// + /// + /// Necessary for objects the server creates as a side effect of creating something else, which + /// therefore have no CREATE statement of their own but still have to be dropped. The temporal + /// history table behind is the motivating case. + /// + protected DatabaseObject(SqlConnection connection, string name, TState state, ExistingObject _) + { + Connection = connection; + State = state; + Name = name; + + EnsureConnectionOpen(); } private void EnsureConnectionOpen() @@ -251,22 +283,127 @@ public static string GenerateShortName(string prefix, bool escape = true) /// /// /// By the time this is called, will be open. - /// Must not throw an exception if the object does not exist. + /// + /// Implementations must be safe to call more than once and must not throw when the object does + /// not exist. They are not required to be exception-free beyond that: the guard against a + /// leftover object is inherently racy (the existence check and the DROP are separate + /// statements), and the connection itself can fail at any moment. owns + /// that problem — it retries on a fresh connection and reports anything it cannot remove — so + /// no failure from here ever escapes to a caller. /// protected abstract void DropObject(); + /// + /// Idempotent, and never throws. These objects are overwhelmingly consumed via using, so + /// a throwing Dispose would surface in place of an exception already in flight and + /// replace a real test failure with a cleanup error — the very "cleanup masks the real failure" + /// problem these types exist to remove. A drop that cannot be completed is reported by + /// instead, which identifies the object and where it lives + /// so the leak stays actionable without destroying the diagnosis of the failure that caused it. + /// public void Dispose() { - if (_shouldDrop) + if (_disposed) { - EnsureConnectionOpen(); - DropObject(); + return; } + + _disposed = true; + + TryDrop(); + // This explicitly does not drop the wrapped SqlConnection; this is sometimes // used in a loop to create multiple UDTs. GC.SuppressFinalize(this); } + + /// + /// Drops the object, swallowing any failure. + /// + /// + /// The drop is all that stands between a failed run and an object orphaned forever in the + /// shared test database, so it gets one retry on a healthy connection before giving up. A + /// failure is never propagated: this runs either during or on a path + /// already unwinding because of a more interesting failure, and in both cases a cleanup error + /// must not replace the exception in flight. + /// + private void TryDrop() + { + try + { + EnsureConnectionOpen(); + DropObject(); + } + catch + { + try + { + TryDropAfterReconnect(); + } + catch + { + // Nothing left to try, and nowhere to report it: even the reporting path failed. + // Swallowing is the whole point — see the remarks above. + } + } + } + + /// + /// Re-attempts the drop on a healthy connection. + /// + /// true if the object was dropped; otherwise false. + /// + /// A drop usually fails because itself has gone bad: a command timeout + /// can leave it unusable, and a transport failure kills it outright. Closing and reopening + /// returns the broken connection to the pool and acquires a healthy one, which is the difference + /// between a transient blip and an object orphaned forever in the shared test database. + /// + /// This deliberately reuses the existing rather than constructing a + /// new one from its connection string: `Persist Security Info` defaults to false, so the password + /// is no longer readable from once it has been + /// opened, and a copy would fail to authenticate. + /// + private bool TryDropAfterReconnect() + { + // Captured before the reconnect attempt: once the connection has been closed or disposed + // these can throw, and this is the only record the leak will leave. + string location = DescribeLocation(); + + try + { + Connection.Close(); + Connection.Open(); + DropObject(); + + return true; + } + catch (Exception ex) + { + // This is the last chance to remove the object, and no caller propagates the failure, + // so this report is the only trace the leak will leave. It names the object *and* + // where it lives, so a human or agent can drop it manually without having to work out + // which server and database the run was pointed at. + Console.WriteLine($"Failed to drop {GetType().Name} '{Name}' {location}; it may be orphaned there. {ex}"); + + return false; + } + } + + /// + /// Describes where the object lives, for the leak report. + /// + private string DescribeLocation() + { + try + { + return $"on data source '{Connection.DataSource}', database '{Connection.Database}'"; + } + catch (Exception ex) + { + return $"on an unknown data source ({ex.GetType().Name} reading the connection)"; + } + } } /// @@ -275,8 +412,13 @@ public void Dispose() /// 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) + protected DatabaseObject(SqlConnection connection, string name, string definition) + : base(connection, name, definition, state: null) + { + } + + protected DatabaseObject(SqlConnection connection, string name, ExistingObject adopt) + : base(connection, name, state: null, adopt) { } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs index a757e9c9e0..20182d1ba3 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/DatabaseUser.cs @@ -22,7 +22,7 @@ public sealed class DatabaseUser : DatabaseObject /// 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) + : base(connection, login.Name, $"FOR LOGIN {login.Name}", state: database) { } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ExistingObject.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ExistingObject.cs new file mode 100644 index 0000000000..93ea622ae8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ExistingObject.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// Selects the constructor that adopts an object which already exists on the server, rather than +/// creating one. +/// +/// +/// The distinction — create this object, or take ownership of one that is already there — cannot be +/// expressed in the signature, since both take the same arguments. A discriminator states it at the +/// call site, which a bool would not; the corresponding public entry point is the +/// AdoptExisting factory. +/// +public enum ExistingObject +{ + Adopt +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/NameIsVerbatim.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/NameIsVerbatim.cs new file mode 100644 index 0000000000..1ad15651e7 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/NameIsVerbatim.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// Distinguishes a constructor that takes a caller-supplied name verbatim from the prefix-based +/// one, which would otherwise have an identical signature. +/// +/// +/// The two overloads differ only in how the string is interpreted — as a name or as a prefix to +/// generate one from — which the type system cannot express. This makes the private constructor +/// unambiguous without inventing a parameter that carries no meaning; the corresponding public +/// entry point is the WithName factory, where the distinction is stated in the name. +/// +internal enum NameIsVerbatim +{ + Yes +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ScalarFunction.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ScalarFunction.cs new file mode 100644 index 0000000000..4044241e76 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ScalarFunction.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more 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 scalar user-defined function, created at the start of its scope and dropped when +/// disposed. +/// +public sealed class ScalarFunction : DatabaseObject +{ + /// + /// Initializes a new instance of the ScalarFunction class using the specified SQL connection, + /// name prefix and definition. + /// + /// + /// If a function 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 function name. + /// The SQL definition of the function, following the function name. + public ScalarFunction(SqlConnection connection, string prefix, string definition) + : base(connection, GenerateLongName(prefix), definition) + { + } + + private ScalarFunction(SqlConnection connection, string name, string definition, NameIsVerbatim _) + : base(connection, name, definition) + { + } + + /// + /// Creates a function using the caller-supplied name verbatim, instead of generating one. + /// + /// + /// Prefer the prefix-based constructor: generated names embed a GUID and so cannot collide + /// between concurrent test runs against a shared database. This overload exists for the + /// minority of tests that must control the name exactly, for example because the same function + /// has to be created and addressed over several different connections. + /// + /// The SQL connection used to interact with the database. + /// The function name, already quoted/escaped by the caller if it needs to be. + /// The SQL definition of the function, following the function name. + public static ScalarFunction WithName(SqlConnection connection, string name, string definition) + => new(connection, name, definition, NameIsVerbatim.Yes); + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE FUNCTION {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + // NOTE: The name is passed to OBJECT_ID() as a parameter rather than being interpolated + // into a string literal, because it may embed Environment.UserName/MachineName (see + // DatabaseObject.GenerateLongName) and an apostrophe in either would break the batch. + // The identifier in DROP FUNCTION is already bracket-quoted. + using SqlCommand dropCommand = new($"IF (OBJECT_ID(@name) IS NOT NULL) DROP FUNCTION {Name}", Connection); + + dropCommand.Parameters.AddWithValue("@name", Name); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Schema.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Schema.cs new file mode 100644 index 0000000000..7f316520b5 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Schema.cs @@ -0,0 +1,65 @@ +// Licensed to the .NET Foundation under one or more 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 schema, created at the start of its scope and dropped when disposed. +/// +/// +/// A schema can only be dropped once it is empty, so any object created inside it must be +/// declared after the schema and therefore disposed before it. +/// +public sealed class Schema : DatabaseObject +{ + /// + /// Initializes a new instance of the Schema class using the specified SQL connection and name prefix. + /// + /// The SQL connection used to interact with the database. + /// The prefix for the schema name. + public Schema(SqlConnection connection, string prefix) + : base(connection, GenerateLongName(prefix), definition: string.Empty) + { + } + + private Schema(SqlConnection connection, string name, NameIsVerbatim _) + : base(connection, name, definition: string.Empty) + { + } + + /// + /// Creates a schema using the caller-supplied name verbatim, instead of generating one. + /// + /// + /// Prefer the prefix-based constructor. This overload exists for tests in which the name + /// itself is under test, for example one containing special characters. + /// + /// The SQL connection used to interact with the database. + /// The schema name, already quoted/escaped by the caller if it needs to be. + public static Schema WithName(SqlConnection connection, string name) + => new(connection, name, NameIsVerbatim.Yes); + + protected override void CreateObject(string definition) + { + // NOTE: CREATE SCHEMA must be the first statement in its batch, so it cannot be guarded + // by an IF the way the other objects are. The base class drops before creating, which + // covers the (vanishingly unlikely) case of a name collision. + using SqlCommand createCommand = new($"CREATE SCHEMA {Name}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + // NOTE: The name is passed to SCHEMA_ID() as a parameter rather than being interpolated + // into a string literal, because it may embed Environment.UserName/MachineName (see + // DatabaseObject.GenerateLongName) and an apostrophe in either would break the batch. + // The identifier in DROP SCHEMA is already bracket-quoted. + using SqlCommand dropCommand = new($"IF (SCHEMA_ID(@name) IS NOT NULL) DROP SCHEMA {Name}", Connection); + + dropCommand.Parameters.AddWithValue("@name", UnescapedName); + + dropCommand.ExecuteNonQuery(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs index ce264a9d14..721cad1b3b 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/ServerLogin.cs @@ -26,7 +26,7 @@ public ServerLogin(SqlConnection connection, string namePrefix, string? defaultD } private ServerLogin(SqlConnection connection, string namePrefix, string password, string? defaultDatabase) - : base(connection, namePrefix, GenerateDefinition(password, defaultDatabase), state: password, shouldCreate: true, shouldDrop: true) + : base(connection, namePrefix, GenerateDefinition(password, defaultDatabase), state: password) { } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs index 0ebbd6cb5e..826c2ccc10 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/StoredProcedure.cs @@ -21,10 +21,30 @@ public sealed class StoredProcedure : DatabaseObject /// 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) + : base(connection, GenerateLongName(prefix), definition) { } + private StoredProcedure(SqlConnection connection, string name, string definition, NameIsVerbatim _) + : base(connection, name, definition) + { + } + + /// + /// Creates a stored procedure using the caller-supplied name verbatim, instead of generating one. + /// + /// + /// Prefer the prefix-based constructor: generated names embed a GUID and so cannot collide + /// between concurrent test runs against a shared database. This overload exists for the + /// minority of tests that must control the name exactly, for example because the same + /// procedure has to be created and addressed over several different connections. + /// + /// The SQL connection used to interact with the database. + /// The procedure name, already quoted/escaped by the caller if it needs to be. + /// The SQL definition of the stored procedure. + public static StoredProcedure WithName(SqlConnection connection, string name, string definition) + => new(connection, name, definition, NameIsVerbatim.Yes); + protected override void CreateObject(string definition) { using SqlCommand createCommand = new($"CREATE PROCEDURE {Name} {definition}", Connection); diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs index 42d07a8fba..b628c3b0fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/Table.cs @@ -21,10 +21,50 @@ public sealed class Table : DatabaseObject /// 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) + : base(connection, GenerateLongName(prefix), definition) { } + private Table(SqlConnection connection, string name, string definition, NameIsVerbatim _) + : base(connection, name, definition) + { + } + + private Table(SqlConnection connection, string name, ExistingObject adopt) + : base(connection, name, adopt) + { + } + + /// + /// Creates a table using the caller-supplied name verbatim, instead of generating one. + /// + /// + /// Prefer the prefix-based constructor: generated names embed a GUID and so cannot collide + /// between concurrent test runs against a shared database. This overload exists for the + /// minority of tests that must control the name exactly - either because the name itself is + /// under test (for example, one containing special characters), or because the same table has + /// to be addressed through several different connections. + /// + /// The SQL connection used to interact with the database. + /// The table name, already quoted/escaped by the caller if it needs to be. + /// The SQL definition describing the structure of the table, including columns and data types. + public static Table WithName(SqlConnection connection, string name, string definition) + => new(connection, name, definition, NameIsVerbatim.Yes); + + /// + /// Adopts a table that already exists, so that it is dropped when the returned instance is + /// disposed. No table is created. + /// + /// + /// For tables the server creates as a side effect of creating something else, and which + /// therefore have no CREATE statement of their own but must still be dropped — the history + /// table behind a being the motivating case. + /// + /// The SQL connection used to drop the table. + /// The table name, already quoted/escaped by the caller if it needs to be. + public static Table AdoptExisting(SqlConnection connection, string name) + => new(connection, name, ExistingObject.Adopt); + protected override void CreateObject(string definition) { using SqlCommand createCommand = new($"CREATE TABLE {Name} {definition}", Connection); diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/TemporalTable.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/TemporalTable.cs new file mode 100644 index 0000000000..6c225938b8 --- /dev/null +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/TemporalTable.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; + +/// +/// A transient system-versioned (temporal) table, created at the start of its scope and dropped — +/// together with its history table — when disposed. +/// +/// +/// A temporal table cannot be dropped directly. System versioning has to be switched off first, +/// which severs the link to the history table and leaves that table behind as an ordinary one that +/// must then be dropped in its own right. That history table is created by the server as a side +/// effect, so it is adopted rather than created: this type +/// owns it and drops it, but never issues a CREATE for it. +/// +public sealed class TemporalTable : DatabaseObject +{ + /// + /// The history table backing this temporal table. Dropped along with it. + /// + public Table HistoryTable { get; } + + /// + /// Initializes a new system-versioned table and adopts the history table created alongside it. + /// + /// The SQL connection used to interact with the database. + /// The prefix for the table name. + /// + /// The prefix for the history table name. SYSTEM_VERSIONING requires a schema-qualified history + /// table, so the generated name is qualified with [dbo]. + /// + /// + /// The column definitions, including the PERIOD FOR SYSTEM_TIME clause, in parentheses. The + /// SYSTEM_VERSIONING option naming the history table is appended automatically. + /// + public TemporalTable(SqlConnection connection, string prefix, string historyPrefix, string columns) + : this(connection, GenerateLongName(prefix), $"[dbo].{GenerateLongName(historyPrefix)}", columns, NameIsVerbatim.Yes) + { + } + + private TemporalTable(SqlConnection connection, string name, string historyName, string columns, NameIsVerbatim _) + : base(connection, name, $"{columns} WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = {historyName}))") + { + // Adopted only once the CREATE above has succeeded, since that statement is what brings the + // history table into existence. If it throws, the base constructor disposes this instance, + // and the null check in DropObject keeps that path safe. + HistoryTable = Table.AdoptExisting(connection, historyName); + } + + protected override void CreateObject(string definition) + { + using SqlCommand createCommand = new($"CREATE TABLE {Name} {definition}", Connection); + + createCommand.ExecuteNonQuery(); + } + + protected override void DropObject() + { + // NOTE: The name is passed to OBJECT_ID() as a parameter rather than being interpolated + // into a string literal, because it embeds Environment.UserName/MachineName (see + // DatabaseObject.GenerateLongName) and an apostrophe in either would break the batch. + // The identifier in ALTER/DROP TABLE is already bracket-quoted by GenerateLongName. + // + // Ordering is load-bearing: SYSTEM_VERSIONING must be switched off before the period can + // be dropped, and the period before the table itself. + using (SqlCommand dropCommand = new($""" + IF (OBJECT_ID(@name) IS NOT NULL) + BEGIN + ALTER TABLE {Name} SET (SYSTEM_VERSIONING = OFF); + ALTER TABLE {Name} DROP PERIOD FOR SYSTEM_TIME; + DROP TABLE {Name}; + END + """, Connection)) + { + dropCommand.Parameters.AddWithValue("@name", Name); + + dropCommand.ExecuteNonQuery(); + } + + // Only reachable once versioning is off, which is what turns the history table back into an + // ordinary droppable one. Null both during the pre-emptive drop the base constructor runs + // before CREATE, and while it unwinds a failed CREATE — in neither case is there an adopted + // history table to drop. + HistoryTable?.Dispose(); + } +} diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs index be68e649dd..95c9f8ebf0 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/DatabaseObjects/UserDefinedType.cs @@ -21,7 +21,7 @@ public sealed class UserDefinedType : DatabaseObject /// 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) + : base(connection, "[dbo]." + GenerateLongName(prefix), definition) { } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs index 4de82d39b7..7fca0a7abb 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/EnclaveAzureDatabaseTests.cs @@ -50,14 +50,25 @@ public EnclaveAzureDatabaseTests(AzureKeyVaultKeyFixture keyVaultKeyFixture) connStrings.Add(connString1.ToString()); connStrings.Add(connString2.ToString()); - foreach (string connString in connStrings) + // NOTE: If creation fails part way through (for example on the second database), this + // constructor never returns, so xUnit never calls Dispose and the keys created so far + // would be leaked. Their names embed a GUID, so nothing would ever reclaim them. + try { - using (SqlConnection connection = new SqlConnection(connString)) + foreach (string connString in connStrings) { - connection.Open(); - databaseObjects.ForEach(o => o.Create(connection)); + using (SqlConnection connection = new SqlConnection(connString)) + { + connection.Open(); + databaseObjects.ForEach(o => o.Create(connection)); + } } } + catch + { + Dispose(); + throw; + } } } @@ -162,13 +173,36 @@ public void Dispose() { if (DataTestUtility.IsEnclaveAzureDatabaseSetup()) { - databaseObjects.Reverse(); + // NOTE: Reverse a copy - reversing the field in place would restore creation order if + // this ever ran twice, dropping the master key before the encryption key that depends + // on it. Every drop is best-effort so that one failure cannot leak the rest. + List objectsToDrop = new List(databaseObjects); + objectsToDrop.Reverse(); + foreach (string connStr in connStrings) { - using (SqlConnection sqlConnection = new SqlConnection(connStr)) + try + { + using (SqlConnection sqlConnection = new SqlConnection(connStr)) + { + sqlConnection.Open(); + + foreach (DbObject databaseObject in objectsToDrop) + { + try + { + databaseObject.Drop(sqlConnection); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(EnclaveAzureDatabaseTests)}: failed to drop '{databaseObject.Name}': {ex.Message}"); + } + } + } + } + catch (Exception ex) { - sqlConnection.Open(); - databaseObjects.ForEach(o => o.Drop(sqlConnection)); + Console.WriteLine($"{nameof(EnclaveAzureDatabaseTests)}: cleanup connection failed: {ex.Message}"); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs index 00e45951db..a7cac6b476 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ExceptionsGenericError.cs @@ -3,7 +3,9 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Data; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted @@ -116,64 +118,100 @@ public sealed class ExceptionGenericErrorFixture : IDisposable static public string encryptedTableName; static public string encryptedProcedureName; + private readonly List _databaseObjects = new(); + private readonly List _connections = new(); + public ExceptionGenericErrorFixture() { SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; - CreateAndPopulateSimpleTable(); + + // NOTE: If setup fails part way through, this constructor never returns, so xUnit never + // calls Dispose and the objects created so far would be leaked. Their names embed a + // GUID, so anything left behind stays in the shared test database forever. + try + { + CreateAndPopulateSimpleTable(); + } + catch + { + Dispose(); + throw; + } } private void CreateAndPopulateSimpleTable() { encryptedTableName = DatabaseHelper.GenerateUniqueName("encrypted"); encryptedProcedureName = DatabaseHelper.GenerateUniqueName("encrypted"); + + // The same table and procedure name has to exist behind every AE connection string, so + // the objects are created with an explicit name rather than a generated one, and the + // connection each was created on is held open for the lifetime of the fixture. foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) { - using (SqlConnection conn = CertificateUtility.GetOpenConnection(false, new SqlConnectionStringBuilder(connectionStr))) + SqlConnection conn = CertificateUtility.GetOpenConnection(false, new SqlConnectionStringBuilder(connectionStr)); + _connections.Add(conn); + + _databaseObjects.Add(Table.WithName(conn, encryptedTableName, "(c1 int)")); + + using (SqlCommand cmdInsert = new SqlCommand($"insert into {encryptedTableName} values(1)", conn)) { - using (SqlCommand cmdCreate = new SqlCommand($"create table {encryptedTableName}(c1 int)", conn)) - { - cmdCreate.CommandType = CommandType.Text; - cmdCreate.ExecuteNonQuery(); - } - using (SqlCommand cmdInsert = new SqlCommand($"insert into {encryptedTableName} values(1)", conn)) - { - cmdInsert.CommandType = CommandType.Text; - cmdInsert.ExecuteNonQuery(); - } - using (SqlCommand cmdCreateProc = new SqlCommand($"create procedure {encryptedProcedureName}(@c1 int) as insert into {encryptedTableName} values (@c1)", conn)) - { - cmdCreateProc.CommandType = CommandType.Text; - cmdCreateProc.ExecuteNonQuery(); - } + cmdInsert.CommandType = CommandType.Text; + cmdInsert.ExecuteNonQuery(); } + + _databaseObjects.Add(StoredProcedure.WithName( + conn, encryptedProcedureName, $"(@c1 int) as insert into {encryptedTableName} values (@c1)")); } } public void Dispose() { // Do NOT remove certificate for concurrent consistency. Certificates are used for other test cases as well. - foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) + + // Disposed in reverse creation order so that each procedure is dropped before the table + // it writes into. + for (int i = _databaseObjects.Count - 1; i >= 0; i--) { - SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder(connectionStr); - using (SqlConnection conn = CertificateUtility.GetOpenConnection(false, sb)) + DisposeSafely(_databaseObjects[i]); + } + _databaseObjects.Clear(); + + foreach (SqlConnection conn in _connections) + { + DisposeSafely(conn); + } + _connections.Clear(); + + // Only use traceoff for non-sysadmin role accounts, Azure accounts does not have the permission. + if (DataTestUtility.IsNotAzureServer()) + { + foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) { - using (SqlCommand cmd = new SqlCommand($"drop table {encryptedTableName}", conn)) + try { - cmd.CommandType = CommandType.Text; - cmd.ExecuteNonQuery(); - - cmd.CommandText = $"drop procedure {encryptedProcedureName}"; - cmd.ExecuteNonQuery(); + CertificateUtility.ChangeServerTceSetting(true, new SqlConnectionStringBuilder(connectionStr)); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(ExceptionGenericErrorFixture)}: failed to reset TCE setting: {ex.Message}"); } } + } + } - // Only use traceoff for non-sysadmin role accounts, Azure accounts does not have the permission. - if (DataTestUtility.IsNotAzureServer()) - { - CertificateUtility.ChangeServerTceSetting(true, sb); - } + private static void DisposeSafely(IDisposable disposable) + { + try + { + disposable.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(ExceptionGenericErrorFixture)}: cleanup failed: {ex.Message}"); } } + } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs index f409a7db77..0ae70c5aca 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/SqlNullValues.cs @@ -7,6 +7,9 @@ using System.Collections.Generic; using System.Data; using Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; +// NOTE: Aliased because the AE test fixtures declare their own Table type alongside the shared one. +using SetupTable = Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted.Setup.Table; using Xunit; namespace Microsoft.Data.SqlClient.ManualTesting.Tests.AlwaysEncrypted @@ -16,6 +19,8 @@ public sealed class SqlNullValuesTests : IClassFixture _databaseObjects = new(); + private readonly List _connections = new(); private string UdfName = DatabaseHelper.GenerateUniqueName("SqlNullValuesRetVal"); private string UdfNameNotNull = DatabaseHelper.GenerateUniqueName("SqlNullValuesRetValNotNull"); @@ -26,35 +31,47 @@ public SqlNullValuesTests(SQLSetupStrategyCertStoreProvider context) // Disable the cache to avoid false failures. SqlConnection.ColumnEncryptionQueryMetadataCacheEnabled = false; - foreach (string connStr in DataTestUtility.AEConnStringsSetup) + // NOTE: If setup fails part way through, this constructor never returns, so xUnit never calls + // Dispose and the functions created so far (whose names embed a GUID) would be leaked. + try { - // Insert data and create functions for SqlNullValues test. - using (SqlConnection sqlConnection = new SqlConnection(connStr)) + foreach (string connStr in DataTestUtility.AEConnStringsSetup) { - sqlConnection.Open(); - - using (SqlCommand cmd = new SqlCommand(string.Format("INSERT INTO [{0}] (c1) VALUES (@c1)", tableName), sqlConnection, null, SqlCommandColumnEncryptionSetting.Enabled)) + // Insert data and create functions for SqlNullValues test. + using (SqlConnection sqlConnection = new SqlConnection(connStr)) { - SqlParameter param = cmd.Parameters.Add("@c1", SqlDbType.Int); - param.Value = DBNull.Value; - cmd.ExecuteNonQuery(); + sqlConnection.Open(); - param.Value = 10; - cmd.ExecuteNonQuery(); - } + using (SqlCommand cmd = new SqlCommand(string.Format("INSERT INTO [{0}] (c1) VALUES (@c1)", tableName), sqlConnection, null, SqlCommandColumnEncryptionSetting.Enabled)) + { + SqlParameter param = cmd.Parameters.Add("@c1", SqlDbType.Int); + param.Value = DBNull.Value; + cmd.ExecuteNonQuery(); - string sql1 = $"CREATE FUNCTION {UdfName}() RETURNS INT AS \n BEGIN \n RETURN (SELECT c1 FROM [{tableName}] WHERE c1 IS NULL)\n END"; - string sql2 = $"CREATE FUNCTION {UdfNameNotNull}() RETURNS INT AS \n BEGIN \n RETURN (SELECT c1 FROM [{tableName}] WHERE c1 IS NOT NULL)\n END"; - using (SqlCommand cmd = sqlConnection.CreateCommand()) - { - cmd.CommandText = sql1; - cmd.ExecuteNonQuery(); + param.Value = 10; + cmd.ExecuteNonQuery(); + } - cmd.CommandText = sql2; - cmd.ExecuteNonQuery(); } + + // The same function names have to exist behind every AE connection string, so + // they are created with an explicit name rather than a generated one, and the + // connection each was created on is held open until cleanup. + SqlConnection functionConnection = new SqlConnection(connStr); + functionConnection.Open(); + _connections.Add(functionConnection); + + _databaseObjects.Add(ScalarFunction.WithName(functionConnection, UdfName, + $"() RETURNS INT AS \n BEGIN \n RETURN (SELECT c1 FROM [{tableName}] WHERE c1 IS NULL)\n END")); + _databaseObjects.Add(ScalarFunction.WithName(functionConnection, UdfNameNotNull, + $"() RETURNS INT AS \n BEGIN \n RETURN (SELECT c1 FROM [{tableName}] WHERE c1 IS NOT NULL)\n END")); } } + catch + { + Dispose(); + throw; + } } [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.IsTargetReadyForAeWithKeyStore))] @@ -156,15 +173,45 @@ public void NullValueTests(string connString, ConnStringColumnEncryptionSetting public void Dispose() { + for (int i = _databaseObjects.Count - 1; i >= 0; i--) + { + TryCleanup(_databaseObjects[i].Dispose); + } + _databaseObjects.Clear(); + + foreach (SqlConnection connection in _connections) + { + TryCleanup(connection.Dispose); + } + _connections.Clear(); + foreach (string connStrAE in DataTestUtility.AEConnStringsSetup) { - using (SqlConnection sqlConnection = new SqlConnection(connStrAE)) + try { - sqlConnection.Open(); - Table.DeleteData(fixture.SqlNullValuesTable.Name, sqlConnection); - DataTestUtility.DropFunction(sqlConnection, UdfName); - DataTestUtility.DropFunction(sqlConnection, UdfNameNotNull); + using (SqlConnection sqlConnection = new SqlConnection(connStrAE)) + { + sqlConnection.Open(); + + TryCleanup(() => SetupTable.DeleteData(fixture.SqlNullValuesTable.Name, sqlConnection)); + } } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SqlNullValuesTests)}: cleanup connection failed: {ex.Message}"); + } + } + } + + private static void TryCleanup(Action cleanupAction) + { + try + { + cleanupAction(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SqlNullValuesTests)}: cleanup step failed: {ex.Message}"); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs index abae589981..7b34a6314e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/ConversionTestFixture.cs @@ -46,16 +46,38 @@ public ConversionTestFixture() _columnMasterKey, _certStoreProvider); - foreach (string connectionStr in DataTestUtility.AEConnStringsSetup) + // NOTE: If creation fails part way through (for example on the second connection string), + // this constructor never returns, so xUnit never disposes the fixture and the keys + // created so far would be leaked into the shared test database forever. Dispose + // explicitly before rethrowing; the drops are guarded, so dropping a key that was + // never created is a no-op. + try { - 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); + 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); + } + } + catch + { + try + { + Dispose(); + } + catch (Exception disposeException) + { + Console.WriteLine( + $"ConversionTestFixture: cleanup after failed setup did not complete: {disposeException.Message}"); + } - using SqlConnection sqlConnection = new SqlConnection(connectionString.ConnectionString); - sqlConnection.Open(); - _columnMasterKey.Create(sqlConnection); - ColumnEncryptionKey.Create(sqlConnection); + throw; } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs index 9929db55d8..5cf8a7b22b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategy.cs @@ -69,6 +69,38 @@ protected SQLSetupStrategy(string customKeyPath) ColumnMasterKeyPath = customKeyPath; } + /// + /// Runs the supplied setup action, disposing this fixture if the action throws. + /// + /// + /// Derived types perform their setup from their constructors. When a constructor throws, + /// xUnit never receives the instance and therefore never disposes it, so every column master + /// key, column encryption key and table created before the failure would be leaked into the + /// (shared, long lived) test database. Because the object names embed a GUID, those leaks are + /// permanent and accumulate on every run, eventually exhausting SQL Server's per-database + /// object identifiers (error 3807). + /// + protected void SetupOrCleanUp(Action setupAction) + { + try + { + setupAction(); + } + catch + { + try + { + Dispose(); + } + catch (Exception disposeException) + { + Console.WriteLine($"{GetType().Name}: cleanup after failed setup did not complete: {disposeException}"); + } + + throw; + } + } + internal virtual void SetupDatabase() { foreach (string value in DataTestUtility.AEConnStringsSetup) @@ -274,16 +306,48 @@ protected List CreateTables(IList columnEncryptionKe protected override void Dispose(bool disposing) { - databaseObjects.Reverse(); - foreach (string value in DataTestUtility.AEConnStringsSetup) + try { - using (SqlConnection sqlConnection = new SqlConnection(value)) + foreach (string value in DataTestUtility.AEConnStringsSetup) { - sqlConnection.Open(); - databaseObjects.ForEach(o => o.Drop(sqlConnection)); + try + { + using (SqlConnection sqlConnection = new SqlConnection(value)) + { + sqlConnection.Open(); + + // NOTE: Objects are dropped in reverse creation order so that dependants + // (tables) are removed before their dependencies (keys). A local copy is + // reversed rather than the field itself, so that repeated disposal (which + // can happen when setup fails) does not restore the original order. + List objectsToDrop = new List(databaseObjects); + objectsToDrop.Reverse(); + + foreach (DbObject databaseObject in objectsToDrop) + { + // Each drop is best-effort: one failure must not prevent the remaining + // objects from being dropped, or the certificate from being removed. + try + { + databaseObject.Drop(sqlConnection); + } + catch (Exception ex) + { + Console.WriteLine($"{GetType().Name}: failed to drop '{databaseObject.Name}': {ex.Message}"); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"{GetType().Name}: failed to clean up database objects: {ex.Message}"); + } } } - base.Dispose(disposing); + finally + { + base.Dispose(disposing); + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs index f1ebc3a93e..eac615af55 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyAzureKeyVault.cs @@ -35,8 +35,12 @@ public SQLSetupStrategyAzureKeyVault() : base() { RegisterGlobalProviders(AkvStoreProvider); } - SetupAzureKeyVault(); - SetupDatabase(); + + SetupOrCleanUp(() => + { + SetupAzureKeyVault(); + SetupDatabase(); + }); } public static void RegisterGlobalProviders(SqlColumnEncryptionAzureKeyVaultProvider akvProvider) @@ -109,18 +113,39 @@ internal override void SetupDatabase() protected override void Dispose(bool disposing) { - base.Dispose(disposing); - - foreach (string keyName in _akvKeyNames) + try { - try - { - _keyClient.StartDeleteKey(keyName); - } - catch (Exception) + base.Dispose(disposing); + } + finally + { + foreach (string keyName in _akvKeyNames) { - continue; + try + { + // NOTE: The deletion is awaited so that the key is actually removed before the + // process exits; StartDeleteKey only begins the (long running) operation. + _keyClient.StartDeleteKey(keyName).WaitForCompletion(); + + // A deleted key stays soft-deleted (still consuming its name and vault quota) + // until purged. Purging is best-effort: the principal may lack permission, or + // the vault may have purge protection enabled. + try + { + _keyClient.PurgeDeletedKey(keyName); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SQLSetupStrategyAzureKeyVault)}: failed to purge AKV key '{keyName}': {ex.Message}"); + } + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SQLSetupStrategyAzureKeyVault)}: failed to delete AKV key '{keyName}': {ex.Message}"); + } } + + _akvKeyNames.Clear(); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs index db935a5fed..eb2b6c3e32 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SQLSetupStrategyCertStoreProvider.cs @@ -16,7 +16,7 @@ public class SQLSetupStrategyCertStoreProvider : SQLSetupStrategy public SQLSetupStrategyCertStoreProvider() : base() { CertStoreProvider = new SqlColumnEncryptionCertificateStoreProvider(); - SetupDatabase(); + SetupOrCleanUp(SetupDatabase); } internal override void SetupDatabase() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnEncryptionKey.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnEncryptionKey.cs index f9b008ddbf..90379e7bbf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnEncryptionKey.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnEncryptionKey.cs @@ -43,12 +43,20 @@ WITH VALUES ( public override void Drop(SqlConnection sqlConnection) { - string sql = $"DROP COLUMN ENCRYPTION KEY [{Name}]"; + // NOTE: The drop is guarded so that cleanup is idempotent. An unguarded DROP throws when + // the key was never created (for example when setup failed part way through), which + // would abort the enclosing drop loop and leak every remaining object. + // NOTE: T-SQL cannot parameterize an identifier, so the name is parameterized in the + // guard - where it is compared as a string - but must be interpolated into the DROP + // itself. The interpolated identifier is bracket-quoted, and the value only ever + // comes from the test's own generated name. + string sql = $"IF EXISTS (SELECT 1 FROM sys.column_encryption_keys WHERE name = @name) DROP COLUMN ENCRYPTION KEY [{Name}]"; using (SqlCommand command = sqlConnection.CreateCommand()) { command.CommandText = sql; command.CommandTimeout = 60; + command.Parameters.AddWithValue("@name", Name); command.ExecuteNonQuery(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnMasterKey.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnMasterKey.cs index 2e8d67b95c..5de2bf4d1f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnMasterKey.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/ColumnMasterKey.cs @@ -50,12 +50,20 @@ public override void Create(SqlConnection sqlConnection) public override void Drop(SqlConnection sqlConnection) { - string sql = $"DROP COLUMN MASTER KEY [{Name}];"; + // NOTE: The drop is guarded so that cleanup is idempotent. An unguarded DROP throws when + // the key was never created (for example when setup failed part way through), which + // would abort the enclosing drop loop and leak every remaining object. + // NOTE: T-SQL cannot parameterize an identifier, so the name is parameterized in the + // guard - where it is compared as a string - but must be interpolated into the DROP + // itself. The interpolated identifier is bracket-quoted, and the value only ever + // comes from the test's own generated name. + string sql = $"IF EXISTS (SELECT 1 FROM sys.column_master_keys WHERE name = @name) DROP COLUMN MASTER KEY [{Name}];"; using (SqlCommand command = sqlConnection.CreateCommand()) { command.CommandText = sql; command.CommandTimeout = 60; + command.Parameters.AddWithValue("@name", Name); command.ExecuteNonQuery(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs index b18e5a0ae8..70def1b1d4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/Setup/Table.cs @@ -12,11 +12,23 @@ protected Table(string name) : base(name) public override void Drop(SqlConnection sqlConnection) { - string sql = $"DROP TABLE [{Name}];"; + // NOTE: The drop is guarded so that cleanup is idempotent. An unguarded DROP throws when + // the object was never created (for example when setup failed part way through), which + // would abort the enclosing drop loop and leak every remaining object. + // NOTE: Both the lookup and the DROP are schema-qualified to [dbo] to match the CREATE + // TABLE statements in the derived classes. An unqualified name resolves against the + // connection's default schema, so if that is not dbo the guard would return NULL and + // silently skip the drop, leaking the table. + // NOTE: T-SQL cannot parameterize an identifier, so the name is parameterized in the + // guard - where it is compared as a string - but must be interpolated into the DROP + // itself. The interpolated identifier is bracket-quoted, and the value only ever + // comes from the test's own generated name. + string sql = $"IF (OBJECT_ID(@name) IS NOT NULL) DROP TABLE [dbo].[{Name}];"; using (SqlCommand command = sqlConnection.CreateCommand()) { command.CommandText = sql; + command.Parameters.AddWithValue("@name", $"[dbo].[{Name}]"); command.ExecuteNonQuery(); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SqlSetupStrategyCspProvider.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SqlSetupStrategyCspProvider.cs index 08cdfe48ec..da6b06d5d8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SqlSetupStrategyCspProvider.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/TestFixtures/SqlSetupStrategyCspProvider.cs @@ -27,7 +27,7 @@ public SQLSetupStrategyCspProvider(CspParameters cspParameters) _cspKeyParameters.Add(cspParameters); CspProvider = new SqlColumnEncryptionCspProvider(); - SetupDatabase(); + SetupOrCleanUp(SetupDatabase); } public SqlColumnEncryptionCspProvider CspProvider { get; } @@ -50,28 +50,35 @@ internal override void SetupDatabase() protected override void Dispose(bool disposing) { - foreach (CspParameters cspParameters in _cspKeyParameters) + try { - try + foreach (CspParameters cspParameters in _cspKeyParameters) { - // Create a new instance of RSACryptoServiceProvider. - // Pass the CspParameters class to use the - // key in the container. - using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParameters); - - // Delete the key entry in the container. - rsaAlg.PersistKeyInCsp = false; - - // Call Clear to release resources and delete the key from the container. - rsaAlg.Clear(); - } - catch (Exception) - { - continue; + try + { + // Create a new instance of RSACryptoServiceProvider. + // Pass the CspParameters class to use the + // key in the container. + using RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider(cspParameters); + + // Delete the key entry in the container. + rsaAlg.PersistKeyInCsp = false; + + // Call Clear to release resources and delete the key from the container. + rsaAlg.Clear(); + } + catch (Exception) + { + continue; + } } - } - base.Dispose(disposing); + _cspKeyParameters.Clear(); + } + finally + { + base.Dispose(disposing); + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs index fad361ff9e..5c8257999b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug84548.cs @@ -4,6 +4,7 @@ using System.Data.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,73 +17,57 @@ 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)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) - { - dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, "CREATE TABLE [" + targetCustomerTable + "] ([CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, CONSTRAINT [PK_" + targetCustomerTable + "] PRIMARY KEY CLUSTERED (CustomerID) ON [PRIMARY]) ON [PRIMARY]"); + using SqlConnection dstConn = new SqlConnection(dstConstr); + dstConn.Open(); - Helpers.TryExecute(dstCmd, - "CREATE TABLE [" + targettable + "] ([OrderID] [int] NOT NULL , " + - " [CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL , " + - " CONSTRAINT [PK_" + targettable + "] PRIMARY KEY CLUSTERED " + - " (" + - " [OrderID]" + - " ) ON [PRIMARY] ," + - " CONSTRAINT [FK_" + targettable + "_Customers] FOREIGN KEY " + - " (" + - " [CustomerID]" + - " ) REFERENCES [" + targetCustomerTable + "] (" + - " [CustomerID]" + - " )" + - ") ON [PRIMARY]"); + // The order table takes a foreign key on the customer table, so it must be dropped first. + // Disposal runs in reverse declaration order, which gives that for free. + using Table customerTable = new Table(dstConn, "SqlBulkCopyTest_Bug84548_customer", + "([CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL," + + " PRIMARY KEY CLUSTERED (CustomerID) ON [PRIMARY]) ON [PRIMARY]"); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - { - srcConn.Open(); + using Table orderTable = new Table(dstConn, "SqlBulkCopyTest_Bug84548", + "([OrderID] [int] NOT NULL," + + " [CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL," + + " PRIMARY KEY CLUSTERED ([OrderID]) ON [PRIMARY]," + + $" FOREIGN KEY ([CustomerID]) REFERENCES {customerTable.Name} ([CustomerID])" + + ") ON [PRIMARY]"); - // First copy the customer ID list across - SqlCommand customerCommand = new SqlCommand("SELECT CustomerID from Customers", srcConn); - using (DbDataReader reader = customerCommand.ExecuteReader()) - { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = targetCustomerTable; - bulkcopy.WriteToServer(reader); - } - } + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + { + srcConn.Open(); - SqlCommand srcCmd = new SqlCommand("select OrderID, CustomerID from Orders where OrderId = 10643", srcConn); - using (DbDataReader reader = srcCmd.ExecuteReader()) - { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = targettable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + // First copy the customer ID list across + using SqlCommand customerCommand = new SqlCommand("SELECT CustomerID from Customers", srcConn); + using (DbDataReader reader = customerCommand.ExecuteReader()) + { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = customerTable.Name; + bulkcopy.WriteToServer(reader); + } + } - ColumnMappings.Add("OrderID", "OrderID"); - ColumnMappings.Add("CustomerID", "CustomerID"); + using SqlCommand srcCmd = new SqlCommand("select OrderID, CustomerID from Orders where OrderId = 10643", srcConn); + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = orderTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - bulkcopy.WriteToServer(reader); + ColumnMappings.Add("OrderID", "OrderID"); + ColumnMappings.Add("CustomerID", "CustomerID"); - DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 1, "Unexpected number of rows."); - } - } + bulkcopy.WriteToServer(reader); + + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 1, "Unexpected number of rows."); } - Helpers.VerifyResults(dstConn, targettable, 2, 1); - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + targettable); - Helpers.TryExecute(dstCmd, "drop table " + targetCustomerTable); } } + + Helpers.VerifyResults(dstConn, orderTable.Name, 2, 1); } } } - diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs index b3de348137..53501c3819 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug85007.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,99 +17,90 @@ 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)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, "CREATE TABLE [" + targetCustomerTable + "] ([CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, CONSTRAINT [PK_" + targetCustomerTable + "] PRIMARY KEY CLUSTERED (CustomerID) ON [PRIMARY]) ON [PRIMARY]"); + // Declared before the order table so it is dropped last: the order table holds a + // foreign key referencing it. + using Table targetCustomerTable = new(dstConn, "SqlBulkCopyTest_Bug85007_customer", + "([CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL, PRIMARY KEY CLUSTERED (CustomerID) ON [PRIMARY]) ON [PRIMARY]"); - Helpers.TryExecute(dstCmd, - "CREATE TABLE [" + dstTable + "] (" + - " [OrderID] [int] IDENTITY (1, 1) NOT NULL ," + - " [CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [EmployeeID] [int] NULL ," + - " [OrderDate] [datetime] NULL ," + - " [RequiredDate] [datetime] NULL ," + - " [ShippedDate] [datetime] NULL ," + - " [ShipVia] [int] NULL ," + - " [Freight] [money] NULL CONSTRAINT [DF_" + dstTable + "_Freight] DEFAULT (0)," + - " [ShipName] [nvarchar] (40) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [ShipAddress] [nvarchar] (60) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [ShipCity] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [ShipRegion] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [ShipPostalCode] [nvarchar] (10) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " [ShipCountry] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + - " CONSTRAINT [PK_" + dstTable + "] PRIMARY KEY CLUSTERED " + - " (" + - " [OrderID]" + - " ) ON [PRIMARY] ," + - " CONSTRAINT [FK_" + dstTable + "_Customers] FOREIGN KEY " + - " (" + - " [CustomerID]" + - " ) REFERENCES [" + targetCustomerTable + "] (" + - " [CustomerID]" + - " )" + - ") ON [PRIMARY]"); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_Bug85007", + "(" + + " [OrderID] [int] IDENTITY (1, 1) NOT NULL ," + + " [CustomerID] [nchar] (5) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [EmployeeID] [int] NULL ," + + " [OrderDate] [datetime] NULL ," + + " [RequiredDate] [datetime] NULL ," + + " [ShippedDate] [datetime] NULL ," + + " [ShipVia] [int] NULL ," + + " [Freight] [money] NULL DEFAULT (0)," + + " [ShipName] [nvarchar] (40) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [ShipAddress] [nvarchar] (60) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [ShipCity] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [ShipRegion] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [ShipPostalCode] [nvarchar] (10) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " [ShipCountry] [nvarchar] (15) COLLATE SQL_Latin1_General_CP1_CI_AS NULL ," + + " PRIMARY KEY CLUSTERED " + + " (" + + " [OrderID]" + + " ) ON [PRIMARY] ," + + " FOREIGN KEY " + + " (" + + " [CustomerID]" + + " ) REFERENCES " + targetCustomerTable.Name + " (" + + " [CustomerID]" + + " )" + + ") ON [PRIMARY]"); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand customerCmd = new SqlCommand("SELECT CustomerID from Customers", srcConn)) - { - srcConn.Open(); + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand customerCmd = new SqlCommand("SELECT CustomerID from Customers", srcConn)) + { + srcConn.Open(); - // First copy the customer ID list across - using (DbDataReader reader = customerCmd.ExecuteReader()) + // First copy the customer ID list across + using (DbDataReader reader = customerCmd.ExecuteReader()) + { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = targetCustomerTable; - bulkcopy.WriteToServer(reader); - } + bulkcopy.DestinationTableName = targetCustomerTable.Name; + bulkcopy.WriteToServer(reader); } + } - SqlCommand srcCmd = new SqlCommand("select * from orders", srcConn); - using (DbDataReader reader = srcCmd.ExecuteReader()) - { + using SqlCommand srcCmd = new SqlCommand("select * from orders", srcConn); + using (DbDataReader reader = srcCmd.ExecuteReader()) + { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 6; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 6; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("OrderID", "OrderID"); - ColumnMappings.Add("CustomerID", "CustomerID"); - ColumnMappings.Add("EmployeeID", "EmployeeID"); - ColumnMappings.Add("RequiredDate", "RequiredDate"); - ColumnMappings.Add("ShippedDate", "ShippedDate"); - ColumnMappings.Add("ShipVia", "ShipVia"); - ColumnMappings.Add("Freight", "Freight"); - ColumnMappings.Add("ShipName", "ShipName"); - ColumnMappings.Add("ShipAddress", "ShipAddress"); - ColumnMappings.Add("ShipCity", "ShipCity"); - ColumnMappings.Add("ShipRegion", "ShipRegion"); - ColumnMappings.Add("ShipPostalCode", "ShipPostalCode"); - ColumnMappings.Add("ShipCountry", "ShipCountry"); + ColumnMappings.Add("OrderID", "OrderID"); + ColumnMappings.Add("CustomerID", "CustomerID"); + ColumnMappings.Add("EmployeeID", "EmployeeID"); + ColumnMappings.Add("RequiredDate", "RequiredDate"); + ColumnMappings.Add("ShippedDate", "ShippedDate"); + ColumnMappings.Add("ShipVia", "ShipVia"); + ColumnMappings.Add("Freight", "Freight"); + ColumnMappings.Add("ShipName", "ShipName"); + ColumnMappings.Add("ShipAddress", "ShipAddress"); + ColumnMappings.Add("ShipCity", "ShipCity"); + ColumnMappings.Add("ShipRegion", "ShipRegion"); + ColumnMappings.Add("ShipPostalCode", "ShipPostalCode"); + ColumnMappings.Add("ShipCountry", "ShipCountry"); - bulkcopy.WriteToServer(reader); + bulkcopy.WriteToServer(reader); - DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 830, "Unexpected number of rows."); - } - Helpers.VerifyResults(dstConn, dstTable, 14, 830); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 830, "Unexpected number of rows."); } + Helpers.VerifyResults(dstConn, dstTable.Name, 14, 830); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - Helpers.TryExecute(dstCmd, "drop table " + targetCustomerTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs index d720fd9ebc..d07f9d8cac 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug903514.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.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,25 +17,13 @@ public class Bug903514 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()) - { - dstConn.Open(); - - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 varchar(7000))"); - } + using SqlConnection dstConn = new SqlConnection(constr); + dstConn.Open(); - DoBulkCopy(constr, dstTable, 2); - DoBulkCopy(constr, dstTable, 0); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_Bug903514", "(col1 int, col2 varchar(7000))"); - using (SqlConnection dstConn = new SqlConnection(constr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) - { - dstConn.Open(); - - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } + DoBulkCopy(constr, dstTable.Name, 2); + DoBulkCopy(constr, dstTable.Name, 0); } private static void DoBulkCopy(string dstConstr, string dstTable, int timeout) diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs index f611e2e3b1..f9764b8de4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Bug98182.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -19,51 +20,38 @@ public void Test() string srctable = "[" + dstTable + " src]"; dstTable = "[" + dstTable + "]"; - string[] epilogue = { - "create table " + srctable + "([col 1] int primary key, [col 2] text)", - "insert into " + srctable + " values (33, 'Michael')", - "create table " + dstTable + "([col 1] int primary key, [col 2] text)", - }; - string[] prologue = { - "drop table " + srctable, - "drop table " + dstTable, - }; - using (SqlConnection dstConn = new SqlConnection(constr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + + using Table srcTableObject = Table.WithName(dstConn, srctable, "([col 1] int primary key, [col 2] text)"); + using Table dstTableObject = Table.WithName(dstConn, dstTable, "([col 1] int primary key, [col 2] text)"); + + Helpers.TryExecute(dstCmd, "insert into " + srctable + " values (33, 'Michael')"); + + using (SqlConnection srcConn = new SqlConnection(constr)) + using (SqlCommand srcCmd = new SqlCommand(string.Format("select * from {0} ", srctable), srcConn)) { - Helpers.ProcessCommandBatch(typeof(SqlConnection), constr, epilogue); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(constr)) - using (SqlCommand srcCmd = new SqlCommand(string.Format("select * from {0} ", srctable), srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; + bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("[col 1]", "col 1"); - ColumnMappings.Add("col 2", "[col 2]"); + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + ColumnMappings.Add("[col 1]", "col 1"); + ColumnMappings.Add("col 2", "[col 2]"); - bulkcopy.WriteToServer(reader); + bulkcopy.WriteToServer(reader); - DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 1, "Unexpected number of rows."); - } - Helpers.VerifyResults(dstConn, dstTable, 2, 1); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 1, "Unexpected number of rows."); } + Helpers.VerifyResults(dstConn, dstTable, 2, 1); } } - finally - { - Helpers.ProcessCommandBatch(typeof(SqlConnection), constr, prologue); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs index 7443a99734..dc72a41a14 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CacheMetadata.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 @@ using System.Data; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -14,7 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy public class CacheMetadata { 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; 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. @@ -23,55 +24,46 @@ 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); using SqlConnection dstConn = new(dstConstr); using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadata", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; + + // First WriteToServer: metadata is queried and cached. + using (SqlConnection srcConn = new(srcConstr)) + { + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); + } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); + + // Second WriteToServer: should reuse cached metadata. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; - - // First WriteToServer: metadata is queried and cached. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); - - // Second WriteToServer: should reuse cached metadata. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 10); - - // Third WriteToServer: should still reuse cached metadata. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 15); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } - finally + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 10); + + // Third WriteToServer: should still reuse cached metadata. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 15); } } @@ -79,7 +71,7 @@ public void Test() public class CacheMetadataInvalidate { 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that ClearCachedMetadata forces a fresh metadata query. @@ -88,47 +80,38 @@ 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); using SqlConnection dstConn = new(dstConstr); using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataInvalidate", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; + + // First WriteToServer: metadata is queried and cached. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; - - // First WriteToServer: metadata is queried and cached. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); - - // Invalidate the cache and write again: should still succeed after re-querying metadata. - bulkcopy.ClearCachedMetadata(); - - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 10); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } - finally + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); + + // Invalidate the cache and write again: should still succeed after re-querying metadata. + bulkcopy.ClearCachedMetadata(); + + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 10); } } @@ -136,7 +119,7 @@ public void Test() public class CacheMetadataDestinationChange { 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; 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. @@ -145,50 +128,38 @@ 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); using SqlConnection dstConn = new(dstConstr); using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable1 = new(dstConn, "SqlBulkCopyTest_CacheMetadataDstChange0", tableDefinition); + using Table dstTable2 = new(dstConn, "SqlBulkCopyTest_CacheMetadataDstChange1", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + + // Write to first table. + bulkcopy.DestinationTableName = dstTable1.Name; + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, initialQuery1); - Helpers.TryExecute(dstCmd, initialQuery2); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - - // Write to first table. - bulkcopy.DestinationTableName = dstTable1; - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable1, 3, 5); - - // Change destination table: cache should be invalidated automatically. - bulkcopy.DestinationTableName = dstTable2; - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable2, 3, 5); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } - finally + Helpers.VerifyResults(dstConn, dstTable1.Name, 3, 5); + + // Change destination table: cache should be invalidated automatically. + bulkcopy.DestinationTableName = dstTable2.Name; + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryDropTable(dstConstr, dstTable1); - Helpers.TryDropTable(dstConstr, dstTable2); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable2.Name, 3, 5); } } @@ -196,7 +167,7 @@ public void Test() public class CacheMetadataWithoutFlag { 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; 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). @@ -205,60 +176,49 @@ 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); using SqlConnection dstConn = new(dstConstr); using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataNoFlag", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn); + bulkcopy.DestinationTableName = dstTable.Name; + + // First WriteToServer without CacheMetadata. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn); - bulkcopy.DestinationTableName = dstTable; - - // First WriteToServer without CacheMetadata. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); - - // Second WriteToServer without CacheMetadata. - using (SqlConnection srcConn = new(srcConstr)) - { - srcConn.Open(); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = srcCmd.ExecuteReader(); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 10); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } - finally + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); + + // Second WriteToServer without CacheMetadata. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + srcConn.Open(); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = srcCmd.ExecuteReader(); + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 10); } } [Trait("Set", "2")] public class CacheMetadataWithDataTable { - private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; + private static readonly string tableDefinition = "(col1 int, col2 nvarchar(50), col3 nvarchar(50))"; // Test that CacheMetadata works with DataTable source as well as IDataReader. [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(); sourceData.Columns.Add("col1", typeof(int)); @@ -272,32 +232,25 @@ public void Test() using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, initialQuery); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataDT", tableDefinition); - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; - // First WriteToServer with DataTable: metadata is queried and cached. - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 3); + // First WriteToServer with DataTable: metadata is queried and cached. + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 3); - // Second WriteToServer with DataTable: should reuse cached metadata. - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 6); - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } + // Second WriteToServer with DataTable: should reuse cached metadata. + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 6); } } [Trait("Set", "2")] public class CacheMetadataColumnMappingsChange { - private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50), col3 nvarchar(50))"; + private static readonly string tableDefinition = "(col1 int, col2 nvarchar(50), col3 nvarchar(50))"; // Test that changing ColumnMappings between WriteToServer calls works correctly with CacheMetadata. // The cached metadata describes the destination table schema, not the column mappings, @@ -306,8 +259,6 @@ public class CacheMetadataColumnMappingsChange 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(); sourceData.Columns.Add("id", typeof(int)); @@ -320,52 +271,45 @@ public void Test() using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataColMap", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; + + // First write: map firstName -> col2, lastName -> col3. + bulkcopy.ColumnMappings.Add("id", "col1"); + bulkcopy.ColumnMappings.Add("firstName", "col2"); + bulkcopy.ColumnMappings.Add("lastName", "col3"); + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 2); + + // Verify first mapping: col2 should contain firstName values. + using (SqlCommand verifyCmd = new("select col2 from " + dstTable.Name + " where col1 = 1", dstConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; - - // First write: map firstName -> col2, lastName -> col3. - bulkcopy.ColumnMappings.Add("id", "col1"); - bulkcopy.ColumnMappings.Add("firstName", "col2"); - bulkcopy.ColumnMappings.Add("lastName", "col3"); - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 2); - - // Verify first mapping: col2 should contain firstName values. - using (SqlCommand verifyCmd = new("select col2 from " + dstTable + " where col1 = 1", dstConn)) - { - object result = verifyCmd.ExecuteScalar(); - Assert.Equal("Alice", result); - } - - // Change mappings: swap col2 and col3 targets. - bulkcopy.ColumnMappings.Clear(); - bulkcopy.ColumnMappings.Add("id", "col1"); - bulkcopy.ColumnMappings.Add("firstName", "col3"); - bulkcopy.ColumnMappings.Add("lastName", "col2"); - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 4); - - // Verify second mapping: col3 should now contain firstName values for the new rows. - using (SqlCommand verifyCmd = new("select col3 from " + dstTable + " where col1 = 1 order by col2", dstConn)) - { - using SqlDataReader reader = verifyCmd.ExecuteReader(); - - // First row (from first write): col3 = "Smith" (lastName). - Assert.True(reader.Read()); - Assert.Equal("Smith", reader.GetString(0)); - - // Second row (from second write): col3 = "Alice" (firstName). - Assert.True(reader.Read()); - Assert.Equal("Alice", reader.GetString(0)); - } + object result = verifyCmd.ExecuteScalar(); + Assert.Equal("Alice", result); } - finally + + // Change mappings: swap col2 and col3 targets. + bulkcopy.ColumnMappings.Clear(); + bulkcopy.ColumnMappings.Add("id", "col1"); + bulkcopy.ColumnMappings.Add("firstName", "col3"); + bulkcopy.ColumnMappings.Add("lastName", "col2"); + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 4); + + // Verify second mapping: col3 should now contain firstName values for the new rows. + using (SqlCommand verifyCmd = new("select col3 from " + dstTable.Name + " where col1 = 1 order by col2", dstConn)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + using SqlDataReader reader = verifyCmd.ExecuteReader(); + + // First row (from first write): col3 = "Smith" (lastName). + Assert.True(reader.Read()); + Assert.Equal("Smith", reader.GetString(0)); + + // Second row (from second write): col3 = "Alice" (firstName). + Assert.True(reader.Read()); + Assert.Equal("Alice", reader.GetString(0)); } } } @@ -373,7 +317,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))"; + private static readonly string tableDefinition = "(col1 int, col2 nvarchar(50), col3 nvarchar(50))"; // Test that mapping a subset of columns on the first call, then all columns on the // second call, works correctly with CacheMetadata. This verifies that null-pruning of @@ -383,8 +327,6 @@ public class CacheMetadataColumnSubsetChange 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(); sourceData.Columns.Add("id", typeof(int)); @@ -397,39 +339,32 @@ public void Test() using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; - - // First write: map only col1 and col2 (col3 is unmatched and will be pruned). - bulkcopy.ColumnMappings.Add("id", "col1"); - bulkcopy.ColumnMappings.Add("firstName", "col2"); - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 2); - - // Second write: map all three columns including col3. - // Without the clone fix, this would fail because col3 metadata was - // permanently nulled in the cache during the first call. - bulkcopy.ColumnMappings.Clear(); - bulkcopy.ColumnMappings.Add("id", "col1"); - bulkcopy.ColumnMappings.Add("firstName", "col2"); - bulkcopy.ColumnMappings.Add("lastName", "col3"); - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 4); - - // Verify col3 has the expected data from the second write. - using (SqlCommand verifyCmd = new("select col3 from " + dstTable + " where col1 = 1 and col3 is not null", dstConn)) - { - object result = verifyCmd.ExecuteScalar(); - Assert.Equal("Smith", result); - } - } - finally + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataSubset", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; + + // First write: map only col1 and col2 (col3 is unmatched and will be pruned). + bulkcopy.ColumnMappings.Add("id", "col1"); + bulkcopy.ColumnMappings.Add("firstName", "col2"); + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 2); + + // Second write: map all three columns including col3. + // Without the clone fix, this would fail because col3 metadata was + // permanently nulled in the cache during the first call. + bulkcopy.ColumnMappings.Clear(); + bulkcopy.ColumnMappings.Add("id", "col1"); + bulkcopy.ColumnMappings.Add("firstName", "col2"); + bulkcopy.ColumnMappings.Add("lastName", "col3"); + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 4); + + // Verify col3 has the expected data from the second write. + using (SqlCommand verifyCmd = new("select col3 from " + dstTable.Name + " where col1 = 1 and col3 is not null", dstConn)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + object result = verifyCmd.ExecuteScalar(); + Assert.Equal("Smith", result); } } } @@ -438,7 +373,7 @@ public void Test() public class CacheMetadataAsync { 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; // Test that CacheMetadata works correctly with WriteToServerAsync. @@ -447,77 +382,66 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CacheMetadataAsync", false); - Task t = TestAsync(srcConstr, dstConstr, dstTable); + Task t = TestAsync(srcConstr, dstConstr); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable) + private static async Task TestAsync(string srcConstr, string dstConstr) { string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); - string initialQuery = string.Format(initialQueryTemplate, dstTable); using SqlConnection dstConn = new(dstConstr); using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataAsync", tableDefinition); + + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); + bulkcopy.DestinationTableName = dstTable.Name; + + // First WriteToServerAsync: metadata is queried and cached. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, initialQuery); - - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata, null); - bulkcopy.DestinationTableName = dstTable; - - // First WriteToServerAsync: metadata is queried and cached. - using (SqlConnection srcConn = new(srcConstr)) - { - await srcConn.OpenAsync().ConfigureAwait(false); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); - await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); - - // Second WriteToServerAsync: should reuse cached metadata. - using (SqlConnection srcConn = new(srcConstr)) - { - await srcConn.OpenAsync().ConfigureAwait(false); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); - await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 10); - - // Third WriteToServerAsync: should still reuse cached metadata. - using (SqlConnection srcConn = new(srcConstr)) - { - await srcConn.OpenAsync().ConfigureAwait(false); - using SqlCommand srcCmd = new(sourceQuery, srcConn); - using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); - await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 15); + await srcConn.OpenAsync().ConfigureAwait(false); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); + await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); } - finally + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); + + // Second WriteToServerAsync: should reuse cached metadata. + using (SqlConnection srcConn = new(srcConstr)) + { + await srcConn.OpenAsync().ConfigureAwait(false); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); + await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); + } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 10); + + // Third WriteToServerAsync: should still reuse cached metadata. + using (SqlConnection srcConn = new(srcConstr)) { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + await srcConn.OpenAsync().ConfigureAwait(false); + using SqlCommand srcCmd = new(sourceQuery, srcConn); + using IDataReader reader = await srcCmd.ExecuteReaderAsync().ConfigureAwait(false); + await bulkcopy.WriteToServerAsync(reader).ConfigureAwait(false); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 15); } } [Trait("Set", "2")] public class CacheMetadataCombinedWithKeepNulls { - private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(50) default 'DefaultVal', col3 nvarchar(50))"; + private static readonly string tableDefinition = "(col1 int, col2 nvarchar(50) default 'DefaultVal', col3 nvarchar(50))"; // Test that CacheMetadata works correctly when combined with other SqlBulkCopyOptions. [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(); sourceData.Columns.Add("col1", typeof(int)); @@ -530,33 +454,26 @@ public void Test() using SqlCommand dstCmd = dstConn.CreateCommand(); dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, initialQuery); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_CacheMetadataKeepNulls", tableDefinition); - using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata | SqlBulkCopyOptions.KeepNulls, null); - bulkcopy.DestinationTableName = dstTable; - bulkcopy.ColumnMappings.Add("col1", "col1"); - bulkcopy.ColumnMappings.Add("col2", "col2"); - bulkcopy.ColumnMappings.Add("col3", "col3"); + using SqlBulkCopy bulkcopy = new(dstConn, SqlBulkCopyOptions.CacheMetadata | SqlBulkCopyOptions.KeepNulls, null); + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.ColumnMappings.Add("col1", "col1"); + bulkcopy.ColumnMappings.Add("col2", "col2"); + bulkcopy.ColumnMappings.Add("col3", "col3"); - // First write with CacheMetadata | KeepNulls. - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 2); + // First write with CacheMetadata | KeepNulls. + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 2); - // Verify nulls were kept (not replaced by default values). - using SqlCommand verifyCmd = new("select col2 from " + dstTable + " where col1 = 1", dstConn); - object result = verifyCmd.ExecuteScalar(); - Assert.Equal(System.DBNull.Value, result); + // Verify nulls were kept (not replaced by default values). + using SqlCommand verifyCmd = new("select col2 from " + dstTable.Name + " where col1 = 1", dstConn); + object result = verifyCmd.ExecuteScalar(); + Assert.Equal(System.DBNull.Value, result); - // Second write should reuse cached metadata. - bulkcopy.WriteToServer(sourceData); - Helpers.VerifyResults(dstConn, dstTable, 3, 4); - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } + // Second write should reuse cached metadata. + bulkcopy.WriteToServer(sourceData); + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 4); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs index ede548f9cf..f99b37c98a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CheckConstraints.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -15,54 +16,44 @@ public class CheckConstraints 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()) { dstConn.Open(); - try - { - // create the source table - Helpers.TryExecute(dstCmd, "create table " + srctable + " (col1 int , col2 int, col3 text)"); - Helpers.TryExecute(dstCmd, "insert into " + srctable + " values (33, 498, 'Michael')"); - Helpers.TryExecute(dstCmd, "insert into " + srctable + " values (34, 499, 'Astrid')"); - Helpers.TryExecute(dstCmd, "insert into " + srctable + " values (65, 500, 'alles Käse')"); - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int primary key, col2 int CONSTRAINT CK_" + dstTable + " CHECK (col2 < 500), col3 text)"); + using Table srctable = new(dstConn, "SqlBulkCopyTest_Extensionsrc", "(col1 int , col2 int, col3 text)"); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_Extensiondst", "(col1 int primary key, col2 int CHECK (col2 < 500), col3 text)"); + + Helpers.TryExecute(dstCmd, "insert into " + srctable.Name + " values (33, 498, 'Michael')"); + Helpers.TryExecute(dstCmd, "insert into " + srctable.Name + " values (34, 499, 'Astrid')"); + Helpers.TryExecute(dstCmd, "insert into " + srctable.Name + " values (65, 500, 'alles Käse')"); - using (SqlConnection srcConn = new SqlConnection(constr)) - using (SqlCommand srcCmd = new SqlCommand("select * from " + srctable, srcConn)) + using (SqlConnection srcConn = new SqlConnection(constr)) + using (SqlCommand srcCmd = new SqlCommand("select * from " + srctable.Name, srcConn)) + { + srcConn.Open(); + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) + try { - try + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.CheckConstraints, null)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.CheckConstraints, null)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("col1", "col1"); - ColumnMappings.Add("col2", "col2"); - ColumnMappings.Add("col3", "col3"); - bulkcopy.WriteToServer(reader); - } - } - catch (SqlException sqlEx) - { - // Error 547 == The %ls statement conflicted with the %ls constraint "%.*ls". - DataTestUtility.AssertEqualsWithDescription(547, sqlEx.Number, "Unexpected error number."); + ColumnMappings.Add("col1", "col1"); + ColumnMappings.Add("col2", "col2"); + ColumnMappings.Add("col3", "col3"); + bulkcopy.WriteToServer(reader); } } + catch (SqlException sqlEx) + { + // Error 547 == The %ls statement conflicted with the %ls constraint "%.*ls". + DataTestUtility.AssertEqualsWithDescription(547, sqlEx.Number, "Unexpected error number."); + } } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - Helpers.TryExecute(dstCmd, "drop table " + srctable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs index 4cffae2fb2..9ffebcd0a2 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ColumnCollation.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -15,14 +16,12 @@ public class ColumnCollation 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()) { dstConn.Open(); - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (name_jp varchar(20) collate Japanese_CI_AS, " + - "name_ru varchar(20) collate Cyrillic_General_CI_AS)"); + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_ColumnCollation", + "(name_jp varchar(20) collate Japanese_CI_AS, name_ru varchar(20) collate Cyrillic_General_CI_AS)"); string s_jp = "江戸糸あやつり人形"; string s_ru = "проверка"; @@ -37,11 +36,11 @@ public void Test() using (SqlBulkCopy bcp = new SqlBulkCopy(dstConn)) { - bcp.DestinationTableName = dstTable; + bcp.DestinationTableName = dstTable.Name; bcp.WriteToServer(table); } - using (SqlDataReader reader = (new SqlCommand("select * from " + dstTable, dstConn)).ExecuteReader()) + using (SqlDataReader reader = (new SqlCommand("select * from " + dstTable.Name, dstConn)).ExecuteReader()) { while (reader.Read()) { @@ -54,16 +53,7 @@ public void Test() "Unexpected value: " + reader["name_ru"]); } } - - } - - using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) - { - dstConn.Open(); - Helpers.TryExecute(dstCmd, "drop table " + dstTable); } } - } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs index 7c82e6dd21..6113fadcb1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader.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 @@ using System.Data.Common; using System.Diagnostics; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -13,73 +14,61 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy [Trait("Set", "2")] public class CopyAllFromReader { - private static readonly string destinationTable = null; private static readonly string sourceTable = "employees"; - private static readonly string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10))"; + private const string TableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; private static readonly string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; [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; - string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); - string initialQuery = string.Format(initialQueryTemplate, dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopyAllFromReader", TableDefinition); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) - { - srcConn.Open(); + srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + IDictionary stats; + long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 13; + long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 15; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - IDictionary stats; - long expectedSelectCount = DataTestUtility.IsAzureSynapse ? 4 : 13; - long expectedSelectRows = DataTestUtility.IsAzureSynapse ? 4 : 15; - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - dstConn.StatisticsEnabled = true; - bulkcopy.WriteToServer(reader); - dstConn.StatisticsEnabled = false; - stats = dstConn.RetrieveStatistics(); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); + bulkcopy.DestinationTableName = dstTable.Name; + dstConn.StatisticsEnabled = true; + bulkcopy.WriteToServer(reader); + dstConn.StatisticsEnabled = false; + stats = dstConn.RetrieveStatistics(); + } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); - Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive."); - Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive."); - DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]); - DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]); - DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]); - DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["CursorOpens"], "Non-zero CursorOpens value: " + (long)stats["CursorOpens"]); - DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["IduRows"], "Non-zero IduRows value: " + (long)stats["IduRows"]); + Assert.True(0 < (long)stats["BytesReceived"], "BytesReceived is non-positive."); + Assert.True(0 < (long)stats["BytesSent"], "BytesSent is non-positive."); + DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["UnpreparedExecs"], "Non-zero UnpreparedExecs value: " + (long)stats["UnpreparedExecs"]); + DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["PreparedExecs"], "Non-zero PreparedExecs value: " + (long)stats["PreparedExecs"]); + DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["Prepares"], "Non-zero Prepares value: " + (long)stats["Prepares"]); + DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["CursorOpens"], "Non-zero CursorOpens value: " + (long)stats["CursorOpens"]); + DataTestUtility.AssertEqualsWithDescription((long)0, (long)stats["IduRows"], "Non-zero IduRows value: " + (long)stats["IduRows"]); - 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(expectedSelectCount, stats["SelectCount"], "Unexpected SelectCount value."); - DataTestUtility.AssertEqualsWithDescription((long)3, stats["ServerRoundtrips"], "Unexpected ServerRoundtrips 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((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(expectedSelectCount, stats["SelectCount"], "Unexpected SelectCount value."); + DataTestUtility.AssertEqualsWithDescription((long)3, stats["ServerRoundtrips"], "Unexpected ServerRoundtrips 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."); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs index 78bc18f17a..0b731df803 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReader1.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,43 +17,34 @@ 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()) { dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopyAllFromReader1", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 * from employees", srcConn)) + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 * from employees", srcConn)) + { + srcConn.Open(); + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("EmployeeID", "col1"); - ColumnMappings.Add("LastName", "col2"); - ColumnMappings.Add("FirstName", "col3"); + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("LastName", "col2"); + ColumnMappings.Add("FirstName", "col3"); - bulkcopy.WriteToServer(reader); + bulkcopy.WriteToServer(reader); - DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 5, "Unexpected number of rows."); - DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied64, (long)5, "Unexpected number of rows."); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 5, "Unexpected number of rows."); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied64, (long)5, "Unexpected number of rows."); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs index d9cc530472..ba1c4f0bbf 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderAsync.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 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -18,56 +19,46 @@ 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); - Task t = TestAsync(srcConstr, dstConstr, dstTable, outputSemaphore); + Task t = TestAsync(srcConstr, dstConstr, outputSemaphore); outputSemaphore.Release(); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable, SemaphoreSlim outputSemaphore) + private static async Task TestAsync(string srcConstr, string dstConstr, SemaphoreSlim outputSemaphore) { - string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10))"; string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; string srcTable = "employees"; string sourceQuery = string.Format(sourceQueryTemplate, srcTable); - string initialQuery = string.Format(initialQueryTemplate, dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_AsyncTest1", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) + srcConn.Open(); + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; + bulkcopy.DestinationTableName = dstTable.Name; - await bulkcopy.WriteToServerAsync(reader); - await outputSemaphore.WaitAsync(); + 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."); - } - Helpers.VerifyResults(dstConn, dstTable, 3, 5); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied, 5, "Unexpected number of rows."); + DataTestUtility.AssertEqualsWithDescription(bulkcopy.RowsCopied64, (long)5, "Unexpected number of rows."); } + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 5); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs index 4329f7f5f4..0ed67a7aba 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderCancelAsync.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 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -20,47 +21,37 @@ 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); + Task t = TestAsync(srcConstr, dstConstr, cts.Token); DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable, CancellationToken ctoken) + private static async Task TestAsync(string srcConstr, string dstConstr, CancellationToken ctoken) { - string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10))"; string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; string srcTable = "employees"; string sourceQuery = string.Format(sourceQueryTemplate, srcTable); - string initialQuery = string.Format(initialQueryTemplate, dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_AsyncTest5", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) - { - srcConn.Open(); + srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - await bulkcopy.WriteToServerAsync(reader, ctoken); - } + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + await bulkcopy.WriteToServerAsync(reader, ctoken); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs index 44b9c89dd7..daf14f4c95 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseAsync.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 @@ using System.Data.Common; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -18,46 +19,38 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_AsyncTest6", false); - Task t = TestAsync(srcConstr, dstConstr, dstTable); + Task t = TestAsync(srcConstr, dstConstr); DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable) + private static async Task TestAsync(string srcConstr, string dstConstr) { - string initialQueryTemplate = "create table {0} (col1 int, col2 nvarchar(20), col3 nvarchar(10))"; + string tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"; string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; string sourceTable = "employees"; string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); - string initialQuery = string.Format(initialQueryTemplate, dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + + using Table dstTable = new(dstConn, "SqlBulkCopyTest_AsyncTest6", tableDefinition); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) - { - srcConn.Open(); + srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - dstConn.Close(); - await bulkcopy.WriteToServerAsync(reader); - } + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + dstConn.Close(); + await bulkcopy.WriteToServerAsync(reader); } } - finally - { - Helpers.TryDropTable(dstConstr, dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs index e48bf5906a..eccc1de75d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyAllFromReaderConnectionCloseOnEventAsync.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 @@ using System.Data.Common; using System.IO; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -19,55 +20,47 @@ 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 tableDefinition = "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 varchar(8000))"; string sourceQuery = "select EmployeeID, LastName, FirstName, REPLICATE('a', 8000) from employees"; - string initialQuery = string.Format(initialQueryTemplate, dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + + using Table dstTable = new(dstConn, "SqlBulkCopyTest_AsyncTest7", tableDefinition); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - Helpers.TryExecute(dstCmd, initialQuery); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) - { - srcConn.Open(); + srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; + bulkcopy.DestinationTableName = dstTable.Name; - // Close the bulk copy's connection when it notifies us - bulkcopy.NotifyAfter = 1; - bulkcopy.SqlRowsCopied += (sender, e) => - { - dstConn.Close(); - }; + // Close the bulk copy's connection when it notifies us + bulkcopy.NotifyAfter = 1; + bulkcopy.SqlRowsCopied += (sender, e) => + { + dstConn.Close(); + }; - using (AsyncDebugScope debugScope = new AsyncDebugScope()) - { - // Force all writes to pend, this will guarantee that we will go through the correct code path - debugScope.ForceAsyncWriteDelay = 1; + using (AsyncDebugScope debugScope = new AsyncDebugScope()) + { + // Force all writes to pend, this will guarantee that we will go through the correct code path + debugScope.ForceAsyncWriteDelay = 1; - // Check that the copying fails - string message = string.Format(SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, "WriteToServer", SystemDataResourceManager.Instance.ADP_ConnectionStateMsg_Closed); - DataTestUtility.AssertThrowsInnerWithAlternate(() => bulkcopy.WriteToServerAsync(reader).Wait(5000), innerExceptionMessage: message); - } + // Check that the copying fails + string message = string.Format(SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, "WriteToServer", SystemDataResourceManager.Instance.ADP_ConnectionStateMsg_Closed); + DataTestUtility.AssertThrowsInnerWithAlternate(() => bulkcopy.WriteToServerAsync(reader).Wait(5000), innerExceptionMessage: message); } } } } - finally - { - Helpers.TryDropTable(dstConstr, dstTable); - } } #endif } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs index 043c0f75f5..7a670d207b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyMultipleReaders.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,53 +17,44 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopyMultipleReaders", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = srcConn.CreateCommand()) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = srcConn.CreateCommand()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - srcConn.Open(); - - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + bulkcopy.DestinationTableName = dstTable.Name; + srcCmd.CommandText = "select EmployeeID, LastName from employees where LastName < 'E%'"; + using (DbDataReader reader = srcCmd.ExecuteReader()) { - bulkcopy.DestinationTableName = dstTable; - srcCmd.CommandText = "select EmployeeID, LastName from employees where LastName < 'E%'"; - using (DbDataReader reader = srcCmd.ExecuteReader()) - { - bulkcopy.WriteToServer(reader); - } - DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); - - srcCmd.CommandText = "select EmployeeID, LastName, FirstName from employees where LastName > 'D%'"; - using (DbDataReader reader = srcCmd.ExecuteReader()) - { - bulkcopy.WriteToServer(reader); - } - DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); + bulkcopy.WriteToServer(reader); + } + DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); - srcCmd.CommandText = "select EmployeeID, FirstName from employees where LastName < 'E%'"; - using (DbDataReader reader = srcCmd.ExecuteReader()) - { - bulkcopy.WriteToServer(reader); - } - DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); + srcCmd.CommandText = "select EmployeeID, LastName, FirstName from employees where LastName > 'D%'"; + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + bulkcopy.WriteToServer(reader); + } + DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); - Helpers.VerifyResults(dstConn, dstTable, 3, 15); + srcCmd.CommandText = "select EmployeeID, FirstName from employees where LastName < 'E%'"; + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + bulkcopy.WriteToServer(reader); } + DataTestUtility.AssertEqualsWithDescription(0, bulkcopy.ColumnMappings.Count, "Unexpected ColumnMappings count."); + + Helpers.VerifyResults(dstConn, dstTable.Name, 3, 15); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs index 324ddad5c8..55b7f34152 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatable.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,64 +17,56 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopySomeFromDataTable", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) - { - srcConn.Open(); + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopySomeFromDataTable", "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) + { + srcConn.Open(); - string columnname; + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; - foreach (DataColumn column in datatable.Columns) - { - columnname = column.ColumnName; - } + string columnname; - datatable.Rows[0].BeginEdit(); - datatable.Rows[0][0] = 333; - datatable.Rows[0].EndEdit(); + foreach (DataColumn column in datatable.Columns) + { + columnname = column.ColumnName; + } - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 7; + datatable.Rows[0].BeginEdit(); + datatable.Rows[0][0] = 333; + datatable.Rows[0].EndEdit(); - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 7; - ColumnMappings.Add(0, "col1"); - ColumnMappings.Add(1, "col2"); - ColumnMappings.Add(2, "col3"); - bulkcopy.WriteToServer(datatable, DataRowState.Unchanged); - datatable.Rows.GetEnumerator().Reset(); - bulkcopy.WriteToServer(datatable, DataRowState.Modified); - datatable.Rows.GetEnumerator().Reset(); - bulkcopy.WriteToServer(datatable, DataRowState.Deleted); - bulkcopy.Close(); - } + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - Helpers.VerifyResults(dstConn, dstTable, 4, 18); + ColumnMappings.Add(0, "col1"); + ColumnMappings.Add(1, "col2"); + ColumnMappings.Add(2, "col3"); + bulkcopy.WriteToServer(datatable, DataRowState.Unchanged); + datatable.Rows.GetEnumerator().Reset(); + bulkcopy.WriteToServer(datatable, DataRowState.Modified); + datatable.Rows.GetEnumerator().Reset(); + bulkcopy.WriteToServer(datatable, DataRowState.Deleted); + bulkcopy.Close(); } - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + + Helpers.VerifyResults(dstConn, dstTable.Name, 4, 18); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs index 7a5eb6d8e7..65531fc03c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromDatatableAsync.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 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -18,69 +19,61 @@ 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); - Task t = TestAsync(srcConstr, dstConstr, dstTable, outputSemaphore); + Task t = TestAsync(srcConstr, dstConstr, outputSemaphore); outputSemaphore.Release(); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable, SemaphoreSlim outputSemaphore) + private static async Task TestAsync(string srcConstr, string dstConstr, SemaphoreSlim outputSemaphore) { DataSet dataset; SqlDataAdapter adapter; DataTable datatable; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try - { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) - { - srcConn.Open(); + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_AsyncTest3", "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) + { + srcConn.Open(); - datatable.Rows[0].BeginEdit(); - datatable.Rows[0][0] = 333; - datatable.Rows[0].EndEdit(); + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 7; + datatable.Rows[0].BeginEdit(); + datatable.Rows[0][0] = 333; + datatable.Rows[0].EndEdit(); - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 7; - ColumnMappings.Add(0, "col1"); - ColumnMappings.Add(1, "col2"); - ColumnMappings.Add(2, "col3"); - bulkcopy.WriteToServer(datatable, DataRowState.Unchanged); - datatable.Rows.GetEnumerator().Reset(); - await bulkcopy.WriteToServerAsync(datatable, DataRowState.Modified); - datatable.Rows.GetEnumerator().Reset(); - await bulkcopy.WriteToServerAsync(datatable, DataRowState.Deleted); - bulkcopy.Close(); - } + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - await outputSemaphore.WaitAsync(); - Helpers.VerifyResults(dstConn, dstTable, 4, 18); + ColumnMappings.Add(0, "col1"); + ColumnMappings.Add(1, "col2"); + ColumnMappings.Add(2, "col3"); + bulkcopy.WriteToServer(datatable, DataRowState.Unchanged); + datatable.Rows.GetEnumerator().Reset(); + await bulkcopy.WriteToServerAsync(datatable, DataRowState.Modified); + datatable.Rows.GetEnumerator().Reset(); + await bulkcopy.WriteToServerAsync(datatable, DataRowState.Deleted); + bulkcopy.Close(); } - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + + await outputSemaphore.WaitAsync(); + Helpers.VerifyResults(dstConn, dstTable.Name, 4, 18); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs index 9d52892baf..02d101f4e9 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromReader.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,45 +17,36 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopySomeFromReader", "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 6; + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 6; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("EmployeeID", "col1"); - ColumnMappings.Add("BirthDate", "col4"); - ColumnMappings.Add("FirstName", "col2"); - ColumnMappings.Add("LastName", "col3"); + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("BirthDate", "col4"); + ColumnMappings.Add("FirstName", "col2"); + ColumnMappings.Add("LastName", "col3"); - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable, 4, 9); + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable.Name, 4, 9); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs index be48bf4351..4e6aa12cda 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArray.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,55 +17,46 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopySomeFromRowArray", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; DataRow[] rows; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopySomeFromRowArray", "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; + rows = new DataRow[datatable.Rows.Count]; + for (int i = 0; i < rows.Length; i++) { - srcConn.Open(); - - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; - rows = new DataRow[datatable.Rows.Count]; - for (int i = 0; i < rows.Length; i++) - { - rows[i] = datatable.Rows[i]; - } + rows[i] = datatable.Rows[i]; + } - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 4; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 4; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add(0, "col1"); - ColumnMappings.Add(2, "col3"); + ColumnMappings.Add(0, "col1"); + ColumnMappings.Add(2, "col3"); - bulkcopy.WriteToServer(rows); - bulkcopy.Close(); - } - Helpers.VerifyResults(dstConn, dstTable, 4, 9); + bulkcopy.WriteToServer(rows); + bulkcopy.Close(); } - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + Helpers.VerifyResults(dstConn, dstTable.Name, 4, 9); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs index b74144fc77..184e109c7f 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopySomeFromRowArrayAsync.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 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -18,17 +19,16 @@ 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); - Task t = TestAsync(srcConstr, dstConstr, dstTable, outputSemaphore); + Task t = TestAsync(srcConstr, dstConstr, outputSemaphore); outputSemaphore.Release(); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable, SemaphoreSlim outputSemaphore) + private static async Task TestAsync(string srcConstr, string dstConstr, SemaphoreSlim outputSemaphore) { DataSet dataset; SqlDataAdapter adapter; @@ -36,49 +36,41 @@ private static async Task TestAsync(string srcConstr, string dstConstr, string d DataRow[] rows; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_AsyncTest2", "(col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10), col4 datetime)"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select * from employees", srcConn)) + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; + rows = new DataRow[datatable.Rows.Count]; + for (int i = 0; i < rows.Length; i++) { - srcConn.Open(); - - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; - rows = new DataRow[datatable.Rows.Count]; - for (int i = 0; i < rows.Length; i++) - { - rows[i] = datatable.Rows[i]; - } + rows[i] = datatable.Rows[i]; + } - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.BatchSize = 4; + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.BatchSize = 4; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add(0, "col1"); - ColumnMappings.Add(2, "col3"); + ColumnMappings.Add(0, "col1"); + ColumnMappings.Add(2, "col3"); - await bulkcopy.WriteToServerAsync(rows); - bulkcopy.Close(); - } - await outputSemaphore.WaitAsync(); - Helpers.VerifyResults(dstConn, dstTable, 4, 9); + await bulkcopy.WriteToServerAsync(rows); + bulkcopy.Close(); } - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + await outputSemaphore.WaitAsync(); + Helpers.VerifyResults(dstConn, dstTable.Name, 4, 9); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs index d690f849ca..f0654c57a7 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyVariants.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -15,68 +16,60 @@ public class CopyVariants 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)", - - "insert into " + dstTable + "_src values (0, null)", - "insert into " + dstTable + "_src values (1, convert(int, 0))", - "insert into " + dstTable + "_src values (2, convert(smallint, -32768))", - "insert into " + dstTable + "_src values (3, convert(real, 2.2))", - "insert into " + dstTable + "_src values (4, convert(float, -3303.33303))", - "insert into " + dstTable + "_src values (5, convert(decimal(28,4), 44404.4404))", - "insert into " + dstTable + "_src values (6, convert(money, $555505.5505) )", - "insert into " + dstTable + "_src values (7, convert(smallmoney, $-6.6606) )", - "insert into " + dstTable + "_src values (8, convert(bit, 1) )", - "insert into " + dstTable + "_src values (9, convert(tinyint, 8) )", - "insert into " + dstTable + "_src values (10, convert(uniqueidentifier, '00000000-0000-0000-0000-000000000009') )", - "insert into " + dstTable + "_src values (11, convert(varbinary(756), 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0A) )", - "insert into " + dstTable + "_src values (12, convert(varchar(756), '111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101') )", - "insert into " + dstTable + "_src values (13, convert(nvarchar(756), N'???a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a?????') )", - "insert into " + dstTable + "_src values (14, convert(datetime, {ts '2003-01-11 12:54:01.133'}) )", - "insert into " + dstTable + "_src values (15, convert(bigint, 444444444444404) )", - "insert into " + dstTable + "_src values (16, convert(int, -555505) )", - "insert into " + dstTable + "_src values (17, convert(smallint, 16) )", - "insert into " + dstTable + "_src values (18, convert(real, 777707.7) )", - "insert into " + dstTable + "_src values (19, convert(float, -888888808.88018) )", - "insert into " + dstTable + "_src values (20, convert(decimal(28,4), 99999999999999999909.9019) )", - - "create table " + dstTable + "_dst (col_1 int primary key, col_2 sql_variant)", - }; using (SqlConnection dstConn = new SqlConnection(constr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table srcTable = new(dstConn, "SqlBulkCopyTest_Variants_src", "(col_1 int primary key, col_2 sql_variant)"); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_Variants_dst", "(col_1 int primary key, col_2 sql_variant)"); + + string[] prologue = { - foreach (string cmdtext in prologue) - { - Helpers.TryExecute(dstCmd, cmdtext); - } - using (SqlConnection srcConn = new SqlConnection(constr)) - using (SqlCommand srcCmd = new SqlCommand("select * from " + dstTable + "_src", srcConn)) - { - srcConn.Open(); + "insert into " + srcTable.Name + " values (0, null)", + "insert into " + srcTable.Name + " values (1, convert(int, 0))", + "insert into " + srcTable.Name + " values (2, convert(smallint, -32768))", + "insert into " + srcTable.Name + " values (3, convert(real, 2.2))", + "insert into " + srcTable.Name + " values (4, convert(float, -3303.33303))", + "insert into " + srcTable.Name + " values (5, convert(decimal(28,4), 44404.4404))", + "insert into " + srcTable.Name + " values (6, convert(money, $555505.5505) )", + "insert into " + srcTable.Name + " values (7, convert(smallmoney, $-6.6606) )", + "insert into " + srcTable.Name + " values (8, convert(bit, 1) )", + "insert into " + srcTable.Name + " values (9, convert(tinyint, 8) )", + "insert into " + srcTable.Name + " values (10, convert(uniqueidentifier, '00000000-0000-0000-0000-000000000009') )", + "insert into " + srcTable.Name + " values (11, convert(varbinary(756), 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA0A) )", + "insert into " + srcTable.Name + " values (12, convert(varchar(756), '111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101') )", + "insert into " + srcTable.Name + " values (13, convert(nvarchar(756), N'???a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a???????????????????????????????üböuaäZßABCÄboÜOUÖvrhÃã??z?????????????????z?????????A?????a?????') )", + "insert into " + srcTable.Name + " values (14, convert(datetime, {ts '2003-01-11 12:54:01.133'}) )", + "insert into " + srcTable.Name + " values (15, convert(bigint, 444444444444404) )", + "insert into " + srcTable.Name + " values (16, convert(int, -555505) )", + "insert into " + srcTable.Name + " values (17, convert(smallint, 16) )", + "insert into " + srcTable.Name + " values (18, convert(real, 777707.7) )", + "insert into " + srcTable.Name + " values (19, convert(float, -888888808.88018) )", + "insert into " + srcTable.Name + " values (20, convert(decimal(28,4), 99999999999999999909.9019) )", + + }; - using (DbDataReader reader = srcCmd.ExecuteReader()) + foreach (string cmdtext in prologue) + { + Helpers.TryExecute(dstCmd, cmdtext); + } + using (SqlConnection srcConn = new SqlConnection(constr)) + using (SqlCommand srcCmd = new SqlCommand("select * from " + srcTable.Name, srcConn)) + { + srcConn.Open(); + + using (DbDataReader reader = srcCmd.ExecuteReader()) + { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable + "_dst"; - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, dstTable + "_dst", 2, 21); + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.WriteToServer(reader); } + Helpers.VerifyResults(dstConn, dstTable.Name, 2, 21); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable + "_src"); - Helpers.TryExecute(dstCmd, "drop table " + dstTable + "_dst"); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs index fedfde00b9..80d51af2e8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -24,62 +25,53 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_CopyWithEvent", false); DataSet dataset; SqlDataAdapter adapter; DataTable datatable; DataRow[] rows; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopyWithEvent", "(orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; + rows = new DataRow[datatable.Rows.Count]; + for (int i = 0; i < rows.Length; i++) { - srcConn.Open(); - - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; - rows = new DataRow[datatable.Rows.Count]; - for (int i = 0; i < rows.Length; i++) - { - rows[i] = datatable.Rows[i]; - } + rows[i] = datatable.Rows[i]; } + } - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { - bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); + bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); - bulkcopy.DestinationTableName = dstTable; - bulkcopy.NotifyAfter = 50; + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.NotifyAfter = 50; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add(0, "orderid"); - ColumnMappings.Add(1, "customerid"); - ColumnMappings.Add(4, "rdate"); - ColumnMappings.Add(7, "freight"); - ColumnMappings.Add(8, "shipname"); + ColumnMappings.Add(0, "orderid"); + ColumnMappings.Add(1, "customerid"); + ColumnMappings.Add(4, "rdate"); + ColumnMappings.Add(7, "freight"); + ColumnMappings.Add(8, "shipname"); - bulkcopy.WriteToServer(rows); - bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); - } - Helpers.VerifyResults(dstConn, dstTable, 5, 100); - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + bulkcopy.WriteToServer(rows); + bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); } + Helpers.VerifyResults(dstConn, dstTable.Name, 5, 100); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs index 4388594033..63adeb7f41 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEvent1.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -38,48 +39,39 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_CopyWithEvent1", "(orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) - { - bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); + bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); - bulkcopy.DestinationTableName = dstTable; - bulkcopy.NotifyAfter = 50; + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.NotifyAfter = 50; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("OrderID", "orderid"); - ColumnMappings.Add("CustomerID", "customerid"); - ColumnMappings.Add("RequiredDate", "rdate"); - ColumnMappings.Add("Freight", "freight"); - ColumnMappings.Add("ShipName", "shipname"); + ColumnMappings.Add("OrderID", "orderid"); + ColumnMappings.Add("CustomerID", "customerid"); + ColumnMappings.Add("RequiredDate", "rdate"); + ColumnMappings.Add("Freight", "freight"); + ColumnMappings.Add("ShipName", "shipname"); - bulkcopy.NotifyAfter = 3; - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); - bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); - bulkcopy.Close(); - } + bulkcopy.NotifyAfter = 3; + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); + bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); + bulkcopy.Close(); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs index f552b0610b..1428aa7683 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/CopyWithEventAsync.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 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -26,17 +27,16 @@ 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); - Task t = TestAsync(srcConstr, dstConstr, dstTable, outputSemaphore); + Task t = TestAsync(srcConstr, dstConstr, outputSemaphore); outputSemaphore.Release(); t.Wait(); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable, SemaphoreSlim outputSemaphore) + private static async Task TestAsync(string srcConstr, string dstConstr, SemaphoreSlim outputSemaphore) { DataSet dataset; SqlDataAdapter adapter; @@ -44,56 +44,48 @@ private static async Task TestAsync(string srcConstr, string dstConstr, string d DataRow[] rows; using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_AsyncTest4", "(orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 100 * from orders", srcConn)) + dataset = new DataSet("MyDataSet"); + adapter = new SqlDataAdapter(srcCmd); + adapter.Fill(dataset); + datatable = dataset.Tables[0]; + rows = new DataRow[datatable.Rows.Count]; + for (int i = 0; i < rows.Length; i++) { - srcConn.Open(); - - dataset = new DataSet("MyDataSet"); - adapter = new SqlDataAdapter(srcCmd); - adapter.Fill(dataset); - datatable = dataset.Tables[0]; - rows = new DataRow[datatable.Rows.Count]; - for (int i = 0; i < rows.Length; i++) - { - rows[i] = datatable.Rows[i]; - } + rows[i] = datatable.Rows[i]; } + } - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { - bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); + bulkcopy.SqlRowsCopied += new SqlRowsCopiedEventHandler(OnRowCopied); - bulkcopy.DestinationTableName = dstTable; - bulkcopy.NotifyAfter = 50; + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.NotifyAfter = 50; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add(0, "orderid"); - ColumnMappings.Add(1, "customerid"); - ColumnMappings.Add(4, "rdate"); - ColumnMappings.Add(7, "freight"); - ColumnMappings.Add(8, "shipname"); + ColumnMappings.Add(0, "orderid"); + ColumnMappings.Add(1, "customerid"); + ColumnMappings.Add(4, "rdate"); + ColumnMappings.Add(7, "freight"); + ColumnMappings.Add(8, "shipname"); - await bulkcopy.WriteToServerAsync(rows); - bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); - } - await outputSemaphore.WaitAsync(); - Helpers.VerifyResults(dstConn, dstTable, 5, 100); - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); + await bulkcopy.WriteToServerAsync(rows); + bulkcopy.SqlRowsCopied -= new SqlRowsCopiedEventHandler(OnRowCopied); } + await outputSemaphore.WaitAsync(); + Helpers.VerifyResults(dstConn, dstTable.Name, 5, 100); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs index 11c4a262a1..1ffa69e5c4 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DataConversionErrorMessageTest.cs @@ -70,7 +70,9 @@ private void DropTable(SqlConnection sqlConnection, string targetTable) using (var command = new SqlCommand()) { command.Connection = sqlConnection; - command.CommandText = string.Format("DROP TABLE {0}", targetTable); + // NOTE: The drop is guarded so that cleanup is idempotent; the table name embeds a + // GUID, so an unguarded drop that throws would leave it behind forever. + command.CommandText = string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) DROP TABLE {0}", targetTable); command.CommandType = CommandType.Text; command.ExecuteNonQuery(); } @@ -79,10 +81,19 @@ private void DropTable(SqlConnection sqlConnection, string targetTable) public void Dispose() { - DropTable(Connection, TableName); - - Connection.Close(); - Connection.Dispose(); + try + { + DropTable(Connection, TableName); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(InitialDatabase)}: failed to drop '{TableName}': {ex.Message}"); + } + finally + { + Connection.Close(); + Connection.Dispose(); + } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs index 230d450f80..1406ae382a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/DestinationTableNameWithSpecialChar.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -26,52 +27,36 @@ public void Test() "[dbo]." + "[" + dsttable + "]", // [dbo].[@sometablename] }; - string[] epilogue = - { - "create table " + srctable + "([col1] int)", - "insert into " + srctable + " values (33)", - "create table [" + dsttable + "]([col1] int)", - }; - - string[] prologue = - { - "drop table " + srctable, - "drop table [" + dsttable + "]", - }; - using (SqlConnection dstConn = new SqlConnection(constr)) using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + + using Table srcTableObject = Table.WithName(dstConn, srctable, "([col1] int)"); + using Table dstTableObject = Table.WithName(dstConn, "[" + dsttable + "]", "([col1] int)"); + + Helpers.TryExecute(dstCmd, "insert into " + srctable + " values (33)"); + + using (SqlConnection srcConn = new SqlConnection(constr)) + using (SqlCommand srcCmd = new SqlCommand(string.Format("select * from {0} ", srctable), srcConn)) { - Helpers.ProcessCommandBatch(typeof(SqlConnection), constr, epilogue); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(constr)) - using (SqlCommand srcCmd = new SqlCommand(string.Format("select * from {0} ", srctable), srcConn)) + int expRows = 1; + foreach (string dsttablename in dsttablecombo) { - srcConn.Open(); - - int expRows = 1; - foreach (string dsttablename in dsttablecombo) + using (DbDataReader reader = srcCmd.ExecuteReader()) { - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dsttablename; - bulkcopy.WriteToServer(reader); - } - Helpers.VerifyResults(dstConn, "[" + dsttable + "]", 1, expRows); + bulkcopy.DestinationTableName = dsttablename; + bulkcopy.WriteToServer(reader); } - expRows++; + Helpers.VerifyResults(dstConn, "[" + dsttable + "]", 1, expRows); } + expRows++; } } - finally - { - Helpers.ProcessCommandBatch(typeof(SqlConnection), constr, prologue); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs index 3ac8636467..8d1b2e90fc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/ErrorOnRowsMarkedAsDeleted.cs @@ -102,7 +102,7 @@ private static void RunCase(SqlConnection conn, string caseName, SqlBulkCopyInpu } // create SQL table with one int field, similar to the above DataTable - SqlCommand cmd = conn.CreateCommand(); + using SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = "CREATE TABLE [" + tableName + "] (IntVal int)"; cmd.ExecuteNonQuery(); @@ -145,10 +145,18 @@ private static void RunCase(SqlConnection conn, string caseName, SqlBulkCopyInpu } finally { - // delete the table - cmd = conn.CreateCommand(); - cmd.CommandText = "DROP TABLE [" + tableName + "]"; - cmd.ExecuteNonQuery(); + // Best-effort: this runs while a test failure may already be propagating, so a + // failed drop must not surface in its place. The table is named instead. + try + { + using SqlCommand dropCmd = conn.CreateCommand(); + dropCmd.CommandText = "IF (OBJECT_ID('[" + tableName + "]') IS NOT NULL) DROP TABLE [" + tableName + "]"; + dropCmd.ExecuteNonQuery(); + } + catch (Exception ex) + { + Console.WriteLine($"Failed to drop table '{tableName}'; it may be orphaned in the test database. {ex}"); + } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs index 6353197634..bf030ecfff 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/FireTrigger.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,22 +17,6 @@ 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 = - { - "create table " + dstTable + "(col1 int, col2 nvarchar(20), col3 nvarchar(10))", - "create table " + dstTable1 + " (col1 int);", - "create trigger " + trigger + " on " + dstTable + " for INSERT as insert into " + dstTable1 + " values (333)" - }; - string[] epilogue = - { - "drop table " + dstTable1 + " ", - "drop trigger " + trigger + " ", - "drop table " + dstTable - }; - string sourceTable = "employees"; string sourceQueryTemplate = "select top 5 EmployeeID, LastName, FirstName from {0}"; string sourceQuery = string.Format(sourceQueryTemplate, sourceTable); @@ -40,41 +25,43 @@ public void Test() using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - Helpers.ProcessCommandBatch(dstCmd, prologue); - try + // Dropping a table also drops the triggers defined on it, so the trigger below + // needs no separate cleanup. dstTable is declared last so that it - and its + // trigger - are dropped before the table the trigger writes into. + using Table dstTable1 = new(dstConn, "SqlBulkCopyTest_FireTrigger_1", "(col1 int)"); + using Table dstTable = new(dstConn, "SqlBulkCopyTest_FireTrigger", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + Helpers.TryExecute(dstCmd, + "create trigger " + DataTestUtility.GetShortName("SqlBulkCopyTest_FireTrigger_2", false) + + " on " + dstTable.Name + " for INSERT as insert into " + dstTable1.Name + " values (333)"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) { - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand(sourceQuery, srcConn)) + srcConn.Open(); + + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); + SqlBulkCopyOptions option = SqlBulkCopyOptions.FireTriggers; - using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, option, null)) { - SqlBulkCopyOptions option = SqlBulkCopyOptions.FireTriggers; - - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, option, null)) - { - bulkcopy.DestinationTableName = dstTable; - bulkcopy.WriteToServer(reader); - } + bulkcopy.DestinationTableName = dstTable.Name; + bulkcopy.WriteToServer(reader); } + } - dstCmd.CommandText = "select top 2 * from " + dstTable1 + " "; - using (DbDataReader reader2 = dstCmd.ExecuteReader()) - { - Assert.True(reader2.Read(), "Failed to read!"); + dstCmd.CommandText = "select top 2 * from " + dstTable1.Name; + using (DbDataReader reader2 = dstCmd.ExecuteReader()) + { + Assert.True(reader2.Read(), "Failed to read!"); - Assert.True(reader2[0] is int, "Unexpected Field(0) type: " + reader2[0].GetType()); + Assert.True(reader2[0] is int, "Unexpected Field(0) type: " + reader2[0].GetType()); - Assert.True((int)(reader2[0]) == 333, "Unexpected Field(0) value: " + reader2[0]); - } + Assert.True((int)(reader2[0]) == 333, "Unexpected Field(0) value: " + reader2[0]); } } - finally - { - Helpers.ProcessCommandBatch(dstCmd, epilogue); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs index 274750bc09..0a447da8b8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Helpers.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,40 +10,6 @@ namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy { public class Helpers { - internal static void ProcessCommandBatch(Type connType, string constr, string[] batch) - { - if (batch.Length > 0) - { - object[] activatorArgs = new object[1]; - activatorArgs[0] = constr; - using (DbConnection conn = (DbConnection)Activator.CreateInstance(connType, activatorArgs)) - { - conn.Open(); - DbCommand cmd = conn.CreateCommand(); - - ProcessCommandBatch(cmd, batch); - } - } - } - - internal static void ProcessCommandBatch(DbCommand cmd, string[] batch) - { - foreach (string cmdtext in batch) - { - Helpers.TryExecute(cmd, cmdtext); - } - } - - public static int TryDropTable(string dstConstr, string tableName) - { - using (SqlConnection dropConn = new SqlConnection(dstConstr)) - using (SqlCommand dropCmd = dropConn.CreateCommand()) - { - dropConn.Open(); - return Helpers.TryExecute(dropCmd, "drop table " + tableName); - } - } - public static int TryExecute(DbCommand cmd, string strText) { cmd.CommandText = strText; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs index 0f18df215e..08c7204127 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/HiddenTargetColumn.cs @@ -4,6 +4,7 @@ using System.Data.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -15,17 +16,13 @@ public class HiddenTargetColumn public void WriteToServer_CopyToHiddenTargetColumn_ThrowsSqlException() { string connectionString = DataTestUtility.TCPConnectionString; - string destinationTable = DataTestUtility.GetShortName("HiddenTargetColumn"); - string destinationHistoryTable = DataTestUtility.GetShortName("HiddenTargetColumn_History"); using (SqlConnection dstConn = new SqlConnection(connectionString)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try - { - DataTestUtility.CreateTable(dstConn, destinationTable, $""" + // Drops the history table as well as the table itself, in the required order. + using TemporalTable destinationTable = new(dstConn, "HiddenTargetColumn", "HiddenTargetColumn_History", """ ( Column1 int primary key not null, Column2 nvarchar(10) not null, @@ -34,42 +31,31 @@ Column2 nvarchar(10) not null, ValidTo datetime2 generated always as row end hidden not null, period for system_time (ValidFrom, ValidTo) ) -with (system_versioning = on(history_table = dbo.{destinationHistoryTable})); """); - using (SqlConnection srcConn = new SqlConnection(connectionString)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, FirstName, LastName, HireDate, sysdatetime() as CurrentDate from employees", srcConn)) - { - srcConn.Open(); + using (SqlConnection srcConn = new SqlConnection(connectionString)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, FirstName, LastName, HireDate, sysdatetime() as CurrentDate from employees", srcConn)) + { + srcConn.Open(); - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = destinationTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) + { + bulkcopy.DestinationTableName = destinationTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("EmployeeID", "Column1"); - ColumnMappings.Add("LastName", "Column2"); - ColumnMappings.Add("FirstName", "Employee's First Name"); - ColumnMappings.Add("HireDate", "ValidFrom"); - ColumnMappings.Add("CurrentDate", "ValidTo"); + ColumnMappings.Add("EmployeeID", "Column1"); + ColumnMappings.Add("LastName", "Column2"); + ColumnMappings.Add("FirstName", "Employee's First Name"); + ColumnMappings.Add("HireDate", "ValidFrom"); + ColumnMappings.Add("CurrentDate", "ValidTo"); - SqlException sqlEx = Assert.Throws(() => bulkcopy.WriteToServer(reader)); + SqlException sqlEx = Assert.Throws(() => bulkcopy.WriteToServer(reader)); - Assert.Equal(13536, sqlEx.Number); - Assert.StartsWith("Cannot insert an explicit value into a GENERATED ALWAYS column in table", sqlEx.Message); - } + Assert.Equal(13536, sqlEx.Number); + Assert.StartsWith("Cannot insert an explicit value into a GENERATED ALWAYS column in table", sqlEx.Message); } } - finally - { - DataTestUtility.RunNonQuery(connectionString, $""" -alter table {destinationTable} set (system_versioning = off); -alter table {destinationTable} drop period for system_time; -"""); - DataTestUtility.DropTable(dstConn, destinationTable); - DataTestUtility.DropTable(dstConn, destinationHistoryTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs index 19bdf2193e..12411f52b5 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/InvalidAccessFromEvent.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.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -54,9 +55,7 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_InvalidAccessFromEvent", false); _dstConstr = dstConstr; - _dstTable = dstTable; DataSet dataset; SqlDataAdapter adapter; @@ -77,30 +76,26 @@ public void Test() _dstConn.Open(); _dstcmd = _dstConn.CreateCommand(); - try - { - Helpers.TryExecute(_dstcmd, "create table " + dstTable + " (orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); - _dstcmd.CommandText = "truncate table " + dstTable; - - expectedErrorMsg = SystemDataResourceManager.Instance.SQL_ConnectionLockedForBcpEvent; - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedRollback)); - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedCommit)); - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedChangeDatabase)); - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedExecute)); - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedBulkCopy)); - - // this will close the connect which is valid so it must be the last test! - expectedErrorMsg = string.Format( - SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, - "WriteToServer", - SystemDataResourceManager.Instance.ADP_ConnectionStateMsg_Closed); - InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedClose)); - } - finally - { - // the original connection is probably trashed - Helpers.TryDropTable(dstConstr, dstTable); - } + // Table disposal falls back to a fresh connection, which matters here because the + // test deliberately leaves the original connection unusable. + using Table dstTable = new(_dstConn, "SqlBulkCopyTest_InvalidAccessFromEvent", + "(orderid int, customerid nchar(5), rdate datetime, freight money, shipname nvarchar(40))"); + _dstTable = dstTable.Name; + _dstcmd.CommandText = "truncate table " + dstTable.Name; + + expectedErrorMsg = SystemDataResourceManager.Instance.SQL_ConnectionLockedForBcpEvent; + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedRollback)); + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedCommit)); + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedChangeDatabase)); + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedExecute)); + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedBulkCopy)); + + // this will close the connect which is valid so it must be the last test! + expectedErrorMsg = string.Format( + SystemDataResourceManager.Instance.ADP_OpenConnectionRequired, + "WriteToServer", + SystemDataResourceManager.Instance.ADP_ConnectionStateMsg_Closed); + InnerTest(new SqlRowsCopiedEventHandler(OnRowCopiedClose)); } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs index 5228c1a35d..4e0c663610 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/KeepNulls.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,42 +17,33 @@ 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(); using SqlCommand dstcmd = destConn.CreateCommand(); - Helpers.TryExecute(dstcmd, "create table " + srctable + " (col1 int, col2 text, col3 text)"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col3) values (1, 'Michael')"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col2, col3) values (2, 'Quark', 'Astrid')"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col2) values (66, 'K�se');"); - Helpers.TryExecute(dstcmd, "create table " + dsttable + " (col1 int identity(1,1), col2 text default 'Jogurt', col3 text)"); + using Table srctable = new(destConn, "SqlBulkCopyTest_KeepNulls0", "(col1 int, col2 text, col3 text)"); + using Table dsttable = new(destConn, "SqlBulkCopyTest_KeepNulls1", "(col1 int identity(1,1), col2 text default 'Jogurt', col3 text)"); + + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col3) values (1, 'Michael')"); + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col2, col3) values (2, 'Quark', 'Astrid')"); + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col2) values (66, 'K�se');"); using SqlConnection sourceConn = new(srcconstr); sourceConn.Open(); - using SqlCommand srccmd = new("select * from " + srctable, sourceConn); + using SqlCommand srccmd = new("select * from " + srctable.Name, sourceConn); using IDataReader reader = srccmd.ExecuteReader(); - try - { - using SqlBulkCopy bulkcopy = new(destConn, SqlBulkCopyOptions.KeepNulls, null); - bulkcopy.DestinationTableName = dsttable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("col1", "col1"); - ColumnMappings.Add("col2", "col2"); - ColumnMappings.Add("col3", "col3"); - - bulkcopy.WriteToServer(reader); - Helpers.VerifyResults(destConn, dsttable, 3, 3); - } - finally - { - Helpers.TryDropTable(dstconstr, srctable); - Helpers.TryDropTable(dstconstr, dsttable); - } + using SqlBulkCopy bulkcopy = new(destConn, SqlBulkCopyOptions.KeepNulls, null); + bulkcopy.DestinationTableName = dsttable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + ColumnMappings.Add("col1", "col1"); + ColumnMappings.Add("col2", "col2"); + ColumnMappings.Add("col3", "col3"); + + bulkcopy.WriteToServer(reader); + Helpers.VerifyResults(destConn, dsttable.Name, 3, 3); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs index ac5651f34f..85c0a4431b 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumn.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,42 +18,33 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_MissingTargetColumn", "(col1 int, col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("EmployeeID", "col1"); - ColumnMappings.Add("LastName", "col2"); // this column does not exist - ColumnMappings.Add("FirstName", "col3"); + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("LastName", "col2"); // this column does not exist + ColumnMappings.Add("FirstName", "col3"); - string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; - errorMsg = string.Format(errorMsg, "col2"); + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col2"); - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); - } + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs index 2728b1efb4..d1f035f1bd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetColumns.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,42 +18,33 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_MissingTargetColumns", "(col1 int, col2 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("EmployeeID", "col1"); - ColumnMappings.Add("LastName", "col3"); // this column does not exist - ColumnMappings.Add("FirstName", "col4"); // this column does not exist + ColumnMappings.Add("EmployeeID", "col1"); + ColumnMappings.Add("LastName", "col3"); // this column does not exist + ColumnMappings.Add("FirstName", "col4"); // this column does not exist - string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; - errorMsg = string.Format(errorMsg, "col3,col4"); + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadNonMatchingColumnName; + errorMsg = string.Format(errorMsg, "col3,col4"); - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); - } + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: errorMsg); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs index af103c1c0a..a8d255224e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/MissingTargetTable.cs @@ -19,7 +19,6 @@ public void Test() string dstConstr = DataTestUtility.TCPConnectionString; string targetTable = DataTestUtility.GetShortName("@SqlBulkCopyTest_MissingTargetTable", false); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs index e8a0571467..3e8b70facc 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/OrderHintTransaction.cs @@ -27,7 +27,7 @@ public void Test() using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - SqlTransaction txn = dstConn.BeginTransaction(); + using SqlTransaction txn = dstConn.BeginTransaction(); dstCmd.Transaction = txn; Helpers.TryExecute(dstCmd, initialQuery); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs index 1c10feebad..f1d82e71c1 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/SpecialCharacterNames.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 Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -28,36 +29,30 @@ public void Test() dstTable = EscapeIdentifier(dstTable); using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + // The table is not currently created inside the schema, but it is declared second so + // that it is dropped first regardless: a schema cannot be dropped while it still + // contains objects. + using Schema schema = Schema.WithName(dstConn, dstschema); + using Table table = Table.WithName(dstConn, dstTable, "(orderid int, customerid nchar(5))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 2 orderid, customerid from orders", srcConn)) { - Helpers.TryExecute(dstCmd, "create schema " + dstschema); - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (orderid int, customerid nchar(5))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 2 orderid, customerid from orders", srcConn)) + using (SqlDataReader srcreader = srcCmd.ExecuteReader()) { - srcConn.Open(); - - using (SqlDataReader srcreader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) { - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn)) - { - bulkcopy.DestinationTableName = dstTable; + bulkcopy.DestinationTableName = dstTable; - bulkcopy.WriteToServer(srcreader); - } + bulkcopy.WriteToServer(srcreader); } - Helpers.VerifyResults(dstConn, dstTable, 2, 2); } - } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - Helpers.TryExecute(dstCmd, "drop schema " + dstschema); + Helpers.VerifyResults(dstConn, dstTable, 2, 2); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs index 69c090d655..f542edc215 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TableLock.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,41 +17,33 @@ 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(); using SqlCommand dstcmd = destConn.CreateCommand(); - Helpers.TryExecute(dstcmd, "create table " + srctable + " (col1 int, col2 text, col3 text)"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col3) values (1, 'Michael')"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col2, col3) values (2, 'Quark', 'Astrid')"); - Helpers.TryExecute(dstcmd, "insert into " + srctable + "(col1, col2) values (66, 'K�se');"); - Helpers.TryExecute(dstcmd, "create table " + dsttable + " (col1 int identity(1,1), col2 text default 'Jogurt', col3 text)"); + using Table srctable = new(destConn, "SqlBulkCopyTest_TableLock0", "(col1 int, col2 text, col3 text)"); + using Table dsttable = new(destConn, "SqlBulkCopyTest_TableLock1", "(col1 int identity(1,1), col2 text default 'Jogurt', col3 text)"); + + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col3) values (1, 'Michael')"); + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col2, col3) values (2, 'Quark', 'Astrid')"); + Helpers.TryExecute(dstcmd, "insert into " + srctable.Name + "(col1, col2) values (66, 'K�se');"); using SqlConnection sourceConn = new(srcconstr); sourceConn.Open(); - using SqlCommand srccmd = new SqlCommand("select * from " + srctable, sourceConn); + using SqlCommand srccmd = new SqlCommand("select * from " + srctable.Name, sourceConn); using IDataReader reader = srccmd.ExecuteReader(); - try - { - using SqlBulkCopy bulkcopy = new(destConn, SqlBulkCopyOptions.TableLock, null); - bulkcopy.DestinationTableName = dsttable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - ColumnMappings.Add("col1", "col1"); - ColumnMappings.Add("col2", "col2"); - ColumnMappings.Add("col3", "col3"); - - bulkcopy.WriteToServer(reader); - Helpers.VerifyResults(destConn, dsttable, 3, 3); - } - finally - { - Helpers.TryDropTable(dstconstr, srctable); - Helpers.TryDropTable(dstconstr, dsttable); - } + + using SqlBulkCopy bulkcopy = new(destConn, SqlBulkCopyOptions.TableLock, null); + bulkcopy.DestinationTableName = dsttable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + ColumnMappings.Add("col1", "col1"); + ColumnMappings.Add("col2", "col2"); + ColumnMappings.Add("col3", "col3"); + + bulkcopy.WriteToServer(reader); + Helpers.VerifyResults(destConn, dsttable.Name, 3, 3); } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs index b5f5e32b53..1df72a8dee 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,43 +18,34 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_Transaction0", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) { - srcConn.Open(); + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) + using SqlTransaction myTrans = dstConn.BeginTransaction(); + try { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - - SqlTransaction myTrans = dstConn.BeginTransaction(); - try - { - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); - } - finally - { - myTrans.Rollback(); - } + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); + } + finally + { + myTrans.Rollback(); } } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs index 665b5a5108..863be57734 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction1.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,47 +18,38 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_Transaction1", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) { - srcConn.Open(); + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + using SqlCommand myCmd = dstConn.CreateCommand(); + myCmd.CommandText = "begin transaction"; + myCmd.ExecuteNonQuery(); - SqlCommand myCmd = dstConn.CreateCommand(); - myCmd.CommandText = "begin transaction"; + try + { + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); + } + finally + { + myCmd.CommandText = "rollback transaction"; myCmd.ExecuteNonQuery(); - - try - { - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader)); - } - finally - { - myCmd.CommandText = "rollback transaction"; - myCmd.ExecuteNonQuery(); - } } } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs index 9c3f69e28a..a99ff3607a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction2.cs @@ -1,9 +1,10 @@ -// 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 Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -16,54 +17,45 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_Transaction2", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) + using SqlTransaction myTrans = dstConn.BeginTransaction(); + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.Default, myTrans)) { - SqlTransaction myTrans = dstConn.BeginTransaction(); - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.Default, myTrans)) - { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - try - { - bulkcopy.WriteToServer(reader); - SqlCommand myCmd = dstConn.CreateCommand(); - myCmd.CommandText = "select * from " + dstTable; - myCmd.Transaction = myTrans; - using (DbDataReader reader1 = myCmd.ExecuteReader()) - { - Assert.True(reader1.HasRows, "Expected reader to have rows."); - } - } - finally + try + { + bulkcopy.WriteToServer(reader); + using SqlCommand myCmd = dstConn.CreateCommand(); + myCmd.CommandText = "select * from " + dstTable.Name; + myCmd.Transaction = myTrans; + using (DbDataReader reader1 = myCmd.ExecuteReader()) { - myTrans.Rollback(); + Assert.True(reader1.HasRows, "Expected reader to have rows."); } } - - Helpers.CheckTableRows(dstConn, dstTable, false); + finally + { + myTrans.Rollback(); + } } + + Helpers.CheckTableRows(dstConn, dstTable.Name, false); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs index f28aaad8c3..4735e7aa3c 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction3.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,48 +18,39 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_Transaction3", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlConnection conn3 = new SqlConnection(srcConstr)) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlConnection conn3 = new SqlConnection(srcConstr)) + conn3.Open(); + // Start a local transaction on the wrong connection. + using SqlTransaction myTrans = conn3.BeginTransaction(); + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.Default, myTrans)) { - conn3.Open(); - // Start a local transaction on the wrong connection. - SqlTransaction myTrans = conn3.BeginTransaction(); - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.Default, myTrans)) - { - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - bulkcopy.DestinationTableName = dstTable; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; + bulkcopy.DestinationTableName = dstTable.Name; - string exceptionMsg = SystemDataResourceManager.Instance.ADP_TransactionConnectionMismatch; - DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: exceptionMsg); + string exceptionMsg = SystemDataResourceManager.Instance.ADP_TransactionConnectionMismatch; + DataTestUtility.AssertThrows(() => bulkcopy.WriteToServer(reader), exceptionMessage: exceptionMsg); - SqlCommand myCmd = dstConn.CreateCommand(); - myCmd.CommandText = "select * from " + dstTable; - myCmd.Transaction = myTrans; + using SqlCommand myCmd = dstConn.CreateCommand(); + myCmd.CommandText = "select * from " + dstTable.Name; + myCmd.Transaction = myTrans; - DataTestUtility.AssertThrows(() => myCmd.ExecuteReader(), exceptionMessage: exceptionMsg); - } + DataTestUtility.AssertThrows(() => myCmd.ExecuteReader(), exceptionMessage: exceptionMsg); } } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs index 2c48368483..7e86f8b640 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/Transaction4.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.Common; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -17,35 +18,26 @@ 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()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_Transaction4", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlConnection conn3 = new SqlConnection(srcConstr)) { - srcConn.Open(); - - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlConnection conn3 = new SqlConnection(srcConstr)) - { - conn3.Open(); - // Start a local transaction on the wrong connection. - SqlTransaction myTrans = conn3.BeginTransaction(); - string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadConflictingTransactionOption; - DataTestUtility.AssertThrows(() => new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, myTrans), exceptionMessage: errorMsg); - } + conn3.Open(); + // Start a local transaction on the wrong connection. + using SqlTransaction myTrans = conn3.BeginTransaction(); + string errorMsg = SystemDataResourceManager.Instance.SQL_BulkLoadConflictingTransactionOption; + DataTestUtility.AssertThrows(() => new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, myTrans), exceptionMessage: errorMsg); } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs index 537f68d8f0..76ce1f9ffd 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/TransactionTestAsync.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 @@ using System.Data.Common; using System.Threading.Tasks; using Microsoft.Data.SqlClient.ManualTesting.Tests; +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; using Xunit; namespace Microsoft.Data.SqlClient.ManualTests.BulkCopy @@ -18,49 +19,40 @@ public void Test() { string srcConstr = DataTestUtility.TCPConnectionString; string dstConstr = DataTestUtility.TCPConnectionString; - string dstTable = DataTestUtility.GetShortName("SqlBulkCopyTest_TransactionTestAsync", false); - Task t = TestAsync(srcConstr, dstConstr, dstTable); + Task t = TestAsync(srcConstr, dstConstr); DataTestUtility.AssertThrowsInner(() => t.Wait()); Assert.True(t.IsCompleted, "Task did not complete! Status: " + t.Status); } - private static async Task TestAsync(string srcConstr, string dstConstr, string dstTable) + private static async Task TestAsync(string srcConstr, string dstConstr) { using (SqlConnection dstConn = new SqlConnection(dstConstr)) - using (SqlCommand dstCmd = dstConn.CreateCommand()) { dstConn.Open(); - try + using Table dstTable = new Table(dstConn, "SqlBulkCopyTest_TransactionTestAsync", "(col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + + using (SqlConnection srcConn = new SqlConnection(srcConstr)) + using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) { - Helpers.TryExecute(dstCmd, "create table " + dstTable + " (col1 int, col2 nvarchar(20), col3 nvarchar(10))"); + srcConn.Open(); - using (SqlConnection srcConn = new SqlConnection(srcConstr)) - using (SqlCommand srcCmd = new SqlCommand("select top 5 EmployeeID, LastName, FirstName from employees", srcConn)) + using (DbDataReader reader = srcCmd.ExecuteReader()) + using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) { - srcConn.Open(); + bulkcopy.DestinationTableName = dstTable.Name; + SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - using (DbDataReader reader = srcCmd.ExecuteReader()) - using (SqlBulkCopy bulkcopy = new SqlBulkCopy(dstConn, SqlBulkCopyOptions.UseInternalTransaction, null)) + using SqlTransaction myTrans = dstConn.BeginTransaction(); + try { - bulkcopy.DestinationTableName = dstTable; - SqlBulkCopyColumnMappingCollection ColumnMappings = bulkcopy.ColumnMappings; - - SqlTransaction myTrans = dstConn.BeginTransaction(); - try - { - await bulkcopy.WriteToServerAsync(reader); - } - finally - { - myTrans.Rollback(); - } + await bulkcopy.WriteToServerAsync(reader); + } + finally + { + myTrans.Rollback(); } } } - finally - { - Helpers.TryExecute(dstCmd, "drop table " + dstTable); - } } } } diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs index 7fd5b36237..f78ea7334d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/BulkCopy/UnprivilegedLogin.cs @@ -52,19 +52,33 @@ public UnprivilegedLogin() // 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()) + // NOTE: If setup fails part way through, this constructor never returns, so xUnit never calls + // Dispose and the connection plus the login/users created so far would be leaked. A server + // login in particular is instance-wide, so it survives long after the test database is + // recreated. Opening the connection is inside the try for the same reason: a failed Open + // still leaves a SqlConnection instance that nothing else will ever dispose. + try { - permissionsModificationCommand.CommandText = $"DENY SELECT ON [master].[sys].[all_columns] TO {_unprivilegedMasterUser.Name}"; - permissionsModificationCommand.ExecuteNonQuery(); + _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(); + permissionsModificationCommand.CommandText = $"DENY SELECT ON [{_managementConnection.Database}].[sys].[all_columns] TO {_unprivilegedAppUser.Name}"; + permissionsModificationCommand.ExecuteNonQuery(); + } + } + catch + { + Dispose(); + throw; } SqlConnectionStringBuilder tcpConnectionBuilder = new(DataTestUtility.TCPConnectionString) @@ -205,9 +219,24 @@ public void BulkCopyWithoutMetadataPermission_FailsWhenUsingAliases() public void Dispose() { - _unprivilegedAppUser?.Dispose(); - _unprivilegedMasterUser?.Dispose(); - _unprivilegedLogin?.Dispose(); + // Each drop is best-effort: a failure to remove one object must not prevent the others (in + // particular the instance-wide server login) from being removed. + DisposeSafely(_unprivilegedAppUser); + DisposeSafely(_unprivilegedMasterUser); + DisposeSafely(_unprivilegedLogin); + _managementConnection?.Dispose(); } + + private static void DisposeSafely(IDisposable? disposable) + { + try + { + disposable?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(UnprivilegedLogin)}: cleanup failed: {ex.Message}"); + } + } } 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 828a87d63e..0dbb7cca8a 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectionTestWithSSLCert/CertificateTestWithTdsServer.cs @@ -64,7 +64,18 @@ public CertificateTestWithTdsServer() Directory.CreateDirectory(s_fullPathToClientCert); } - RunPowershellScript(s_fullPathToPowershellScript); + // NOTE: The setup script installs a certificate into the LocalMachine root store and writes + // the scratch files. If it fails part way through, xUnit never calls Dispose, so run the + // cleanup here to avoid leaving the certificate behind. + try + { + RunPowershellScript(s_fullPathToPowershellScript); + } + catch + { + Dispose(); + throw; + } } private static bool IsLocalHost() @@ -254,18 +265,43 @@ private X509Certificate2 GetEncryptionCertificate(string fileName, string? passw private void RemoveCertificate() { - string thumbprint = File.ReadAllText(s_fullPathTothumbprint); - using X509Store certStore = new(StoreName.Root, StoreLocation.LocalMachine); - certStore.Open(OpenFlags.ReadWrite); - X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); - if (certCollection.Count > 0) + // NOTE: Each step is independent. Previously a missing thumbprint file (or a failed store + // removal) aborted the whole cleanup, leaving the certificate in the LocalMachine root + // store and the scratch directory on disk. + try + { + if (File.Exists(s_fullPathTothumbprint)) + { + string thumbprint = File.ReadAllText(s_fullPathTothumbprint); + using X509Store certStore = new(StoreName.Root, StoreLocation.LocalMachine); + certStore.Open(OpenFlags.ReadWrite); + X509Certificate2Collection certCollection = certStore.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); + if (certCollection.Count > 0) + { + certStore.Remove(certCollection[0]); + } + certStore.Close(); + } + } + catch (Exception ex) { - certStore.Remove(certCollection[0]); + Console.WriteLine($"{nameof(CertificateTestWithTdsServer)}: failed to remove certificate: {ex.Message}"); } - certStore.Close(); - File.Delete(s_fullPathTothumbprint); - Directory.Delete(s_fullPathToClientCert, true); + TryCleanup(() => File.Delete(s_fullPathTothumbprint)); + TryCleanup(() => Directory.Delete(s_fullPathToClientCert, true)); + } + + private static void TryCleanup(Action cleanupAction) + { + try + { + cleanupAction(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(CertificateTestWithTdsServer)}: cleanup step failed: {ex.Message}"); + } } private static void RemoveForceEncryptionFromRegistryPath(string registryPath) @@ -292,8 +328,17 @@ protected virtual void Dispose(bool disposing) { if (disposing && !string.IsNullOrEmpty(s_fullPathTothumbprint)) { - RemoveCertificate(); - RemoveForceEncryptionFromRegistryPath(ForceEncryptionRegistryPath); + // NOTE: The registry/service reset must run even if certificate removal fails, + // otherwise the SQL Server instance is left with ForceEncryption enabled and a + // reference to a certificate that may no longer exist. + try + { + RemoveCertificate(); + } + finally + { + RemoveForceEncryptionFromRegistryPath(ForceEncryptionRegistryPath); + } } } else 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 fbbee8db26..3b42d7b1c8 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs @@ -24,10 +24,10 @@ public static class ConnectivityTest private static readonly string s_dbConnectionString = new SqlConnectionStringBuilder(s_connectionString) { InitialCatalog = s_databaseName }.ConnectionString; private static readonly string s_createDatabaseCmd = $"CREATE DATABASE {s_databaseName}"; private static readonly string s_createTableCmd = $"CREATE TABLE {s_tableName} (NAME NVARCHAR(40), AGE INT)"; - private static readonly string s_alterDatabaseSingleCmd = $"ALTER DATABASE {s_databaseName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"; - private static readonly string s_alterDatabaseMultiCmd = $"ALTER DATABASE {s_databaseName} SET MULTI_USER WITH ROLLBACK IMMEDIATE;"; + private static readonly string s_alterDatabaseSingleCmd = $"IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{s_databaseName}')) ALTER DATABASE {s_databaseName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;"; + private static readonly string s_alterDatabaseMultiCmd = $"IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{s_databaseName}')) ALTER DATABASE {s_databaseName} SET MULTI_USER WITH ROLLBACK IMMEDIATE;"; private static readonly string s_selectTableCmd = $"SELECT COUNT(*) FROM {s_tableName}"; - private static readonly string s_dropDatabaseCmd = $"DROP DATABASE {s_databaseName}"; + private static readonly string s_dropDatabaseCmd = $"IF (EXISTS(SELECT 1 FROM sys.databases WHERE name = '{s_databaseName}')) DROP DATABASE {s_databaseName}"; // Synapse: Stored procedure sp_who2 does not exist or is not supported. // Synapse: SqlConnection.ServerProcessId is always retrieved as 0. @@ -263,8 +263,19 @@ public static void ConnectionKilledTest() } finally { - // Kill all the connections, set Database to SINGLE_USER Mode and drop Database - DataTestUtility.RunNonQuery(s_connectionString, s_alterDatabaseSingleCmd, 4); + // Kill all the connections, set Database to SINGLE_USER Mode and drop Database. + // NOTE: The database name embeds a GUID, so failing to drop it leaks a database that + // nothing will ever reclaim. Switching to SINGLE_USER is only an optimization to + // evict other sessions, so its failure must not prevent the drop from running. + try + { + DataTestUtility.RunNonQuery(s_connectionString, s_alterDatabaseSingleCmd, 4); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(ConnectionKilledTest)}: failed to set '{s_databaseName}' to SINGLE_USER: {ex.Message}"); + } + DataTestUtility.RunNonQuery(s_connectionString, s_dropDatabaseCmd, 4); } } 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 4e5fa29c7d..629ce3785e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/JsonTest/JsonBulkCopyTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.Data.SqlClient.ManualTesting.Tests.SQL.JsonTest { [Trait("Set", "3")] - public class JsonBulkCopyTest + public class JsonBulkCopyTest : IDisposable { private readonly ITestOutputHelper _output; private static readonly string _generatedJsonFile = DataTestUtility.GetShortName("randomRecords"); @@ -28,6 +28,37 @@ public JsonBulkCopyTest(ITestOutputHelper output) _output = output; } + /// + /// Drops the tables and removes the scratch files created by the tests. + /// + /// + /// The table names embed a GUID, so without this the tests left two tables behind in the + /// shared test database on every run. + /// + public void Dispose() + { + try + { + if (DataTestUtility.AreConnStringsSetup() && DataTestUtility.IsJsonSupported) + { + using SqlConnection connection = new SqlConnection(DataTestUtility.TCPConnectionString); + connection.Open(); + + DataTestUtility.DropTable(connection, _sourceTableName); + DataTestUtility.DropTable(connection, _destinationTableName); + } + } + catch (Exception ex) + { + _output.WriteLine($"{nameof(JsonBulkCopyTest)}: failed to drop test tables: {ex.Message}"); + } + finally + { + DeleteFile(_generatedJsonFile); + DeleteFile(_outputFile); + } + } + public static IEnumerable JsonBulkCopyTestData() { yield return new object[] { CommandBehavior.Default, false, 30, 10 }; 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 6f3f74fe4e..4f59823b79 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParallelTransactionsTest/ParallelTransactionsTest.cs @@ -43,25 +43,21 @@ private static void BasicParallelTest(string connectionString, string tempTableN SqlTransaction trans2 = connection.BeginTransaction(); SqlTransaction trans3 = connection.BeginTransaction(); - SqlCommand com1 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com1 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com1.Transaction = trans1; com1.ExecuteNonQuery(); - SqlCommand com2 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com2 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com2.Transaction = trans2; com2.ExecuteNonQuery(); - SqlCommand com3 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com3 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com3.Transaction = trans3; com3.ExecuteNonQuery(); trans1.Rollback(); trans2.Rollback(); trans3.Rollback(); - - com1.Dispose(); - com2.Dispose(); - com3.Dispose(); } } @@ -100,15 +96,15 @@ private static void MultipleExecutesInSameTransactionTest(string connectionStrin SqlTransaction trans2 = connection.BeginTransaction(); SqlTransaction trans3 = connection.BeginTransaction(); - SqlCommand com1 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com1 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com1.Transaction = trans1; com1.ExecuteNonQuery(); - SqlCommand com2 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com2 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com2.Transaction = trans2; com2.ExecuteNonQuery(); - SqlCommand com3 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com3 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com3.Transaction = trans3; com3.ExecuteNonQuery(); @@ -116,15 +112,14 @@ private static void MultipleExecutesInSameTransactionTest(string connectionStrin trans2.Rollback(); trans3.Rollback(); - com1.Dispose(); - com2.Dispose(); - com3.Dispose(); - - SqlCommand com4 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); + using SqlCommand com4 = new SqlCommand("select top 1 EmployeeID from " + tempTableName, connection); com4.Transaction = trans1; - SqlDataReader reader4 = com4.ExecuteReader(); - reader4.Dispose(); - com4.Dispose(); + using (SqlDataReader reader4 = com4.ExecuteReader()) + { + // Scoped deliberately: MARS is off here, so the reader must be closed before + // the rollback below, which would otherwise fail on a connection that still + // has an open reader. + } trans1.Rollback(); } @@ -138,7 +133,7 @@ private static string CreateTempTable(string connectionString) using (var conn = new SqlConnection(connectionString)) { conn.Open(); - SqlCommand cmd = new SqlCommand(string.Format("SELECT EmployeeID, LastName, FirstName, Title, Address, City, Region, PostalCode, Country into {0} from Employees", tempTableName), conn); + using SqlCommand cmd = new SqlCommand(string.Format("SELECT EmployeeID, LastName, FirstName, Title, Address, City, Region, PostalCode, Country into {0} from Employees", tempTableName), conn); cmd.ExecuteNonQuery(); cmd.CommandText = string.Format("alter table {0} add constraint EmployeeID_{1} primary key (EmployeeID)", tempTableName, uniqueKey); cmd.ExecuteNonQuery(); @@ -147,13 +142,26 @@ private static string CreateTempTable(string connectionString) return tempTableName; } + /// + /// Best-effort: both callers invoke this from a finally, so a throw here would surface + /// in place of the failure that is already propagating and hide the real test result. The + /// table is named on failure so the leak stays attributable. + /// private static void DropTempTable(string connectionString, string tempTableName) { - using (SqlConnection con1 = new SqlConnection(connectionString)) + try { - con1.Open(); - SqlCommand cmd = new SqlCommand("Drop table " + tempTableName, con1); - cmd.ExecuteNonQuery(); + using (SqlConnection con1 = new SqlConnection(connectionString)) + { + con1.Open(); + using SqlCommand cmd = new SqlCommand( + string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) DROP TABLE {0}", tempTableName), con1); + cmd.ExecuteNonQuery(); + } + } + catch (Exception ex) + { + Console.WriteLine($"Failed to drop temp table '{tempTableName}'; it may be orphaned in the test database. {ex}"); } } } 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 a8baee786c..2373171a04 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/TvpTest.cs @@ -295,26 +295,16 @@ private void TestTVPPermutations(SteStructuredTypeBoundaries bounds, bool runOnl continue; } - // Send list of SqlDataRecords as value - Console.WriteLine("------IEnumerable---------"); + // NOTE: The server objects created above (a table type and a stored procedure) must be + // dropped even when the body below throws. Their names are unique per permutation, + // so anything left behind stays in the shared test database forever. try { - param.Value = CreateListOfRecords(tvpPerm, baseValues); - ExecuteAndVerify(cmd, tvpPerm, baseValues, null); - } - catch (ArgumentException ae) - { - // some argument exceptions expected and should be swallowed - Console.WriteLine("Argument exception in value setup: {0}", ae.Message); - } - - if (!runOnlyDataRecordTest) - { - // send DbDataReader - Console.WriteLine("------DbDataReader---------"); + // Send list of SqlDataRecords as value + Console.WriteLine("------IEnumerable---------"); try { - param.Value = new TvpRestartableReader(CreateListOfRecords(tvpPerm, baseValues)); + param.Value = CreateListOfRecords(tvpPerm, baseValues); ExecuteAndVerify(cmd, tvpPerm, baseValues, null); } catch (ArgumentException ae) @@ -323,17 +313,35 @@ private void TestTVPPermutations(SteStructuredTypeBoundaries bounds, bool runOnl Console.WriteLine("Argument exception in value setup: {0}", ae.Message); } - // send datasets - Console.WriteLine("------DataTables---------"); - foreach (DataTable d in dtList) + if (!runOnlyDataRecordTest) { - param.Value = d; - ExecuteAndVerify(cmd, tvpPerm, null, d); + // send DbDataReader + Console.WriteLine("------DbDataReader---------"); + try + { + param.Value = new TvpRestartableReader(CreateListOfRecords(tvpPerm, baseValues)); + ExecuteAndVerify(cmd, tvpPerm, baseValues, null); + } + catch (ArgumentException ae) + { + // some argument exceptions expected and should be swallowed + Console.WriteLine("Argument exception in value setup: {0}", ae.Message); + } + + // send datasets + Console.WriteLine("------DataTables---------"); + foreach (DataTable d in dtList) + { + param.Value = d; + ExecuteAndVerify(cmd, tvpPerm, null, d); + } } } - - // And clean up - DropServerObjects(tvpPerm); + finally + { + // And clean up + DropServerObjects(tvpPerm); + } iter++; } @@ -892,11 +900,20 @@ private bool DoesRowMatchMetadata(object[] row, DataTable table) private void DropServerObjects(StePermutation tvpPerm) { - string dropText = "DROP PROC " + GetProcName(tvpPerm) + "; DROP TYPE " + GetTypeName(tvpPerm); using SqlConnection conn = new(_connStr); conn.Open(); - SqlCommand cmd = new(dropText, conn); + // NOTE: The procedure and the type are dropped by separate, individually guarded commands. + // Previously both drops shared a single batch, so when the procedure did not exist (the + // CREATE PROC step failed after CREATE TYPE succeeded) the batch aborted on the first + // statement and the table type was leaked into the shared test database. + DropServerObject(conn, "DROP PROC IF EXISTS " + GetProcName(tvpPerm)); + DropServerObject(conn, "DROP TYPE IF EXISTS " + GetTypeName(tvpPerm)); + } + + private static void DropServerObject(SqlConnection conn, string dropText) + { + using SqlCommand cmd = new(dropText, conn); try { cmd.ExecuteNonQuery(); 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 38295bd953..628ebfefe6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlCredentialTest/SqlCredentialTest.cs @@ -198,18 +198,36 @@ private static void CreateTestUser(string username, string password) private static void DropTestUser(string username) { // Removes a created test user. - string dropUserCmd = $"IF EXISTS (SELECT * FROM sys.schemas WHERE name = '{username}') BEGIN DROP SCHEMA {username} END;" - + $"IF EXISTS (SELECT * FROM sys.database_principals WHERE type = 'S' AND name = '{username}') BEGIN DROP USER {username} END;" - + $"DROP LOGIN {username}"; + // NOTE: Each statement runs independently and is guarded so that a failure to drop the + // schema or database user does not leak the login, which is instance-wide and would + // otherwise survive well beyond the test run. + string[] dropStatements = + { + $"IF EXISTS (SELECT * FROM sys.schemas WHERE name = '{username}') BEGIN DROP SCHEMA {username} END;", + $"IF EXISTS (SELECT * FROM sys.database_principals WHERE type = 'S' AND name = '{username}') BEGIN DROP USER {username} END;", + $"IF EXISTS (SELECT * FROM sys.server_principals WHERE name = '{username}') BEGIN DROP LOGIN {username} END;", + }; // Pool must be cleared to prevent DROP LOGIN failure. SqlConnection.ClearAllPools(); using (var conn = new SqlConnection(DataTestUtility.TCPConnectionString)) - using (var cmd = new SqlCommand(dropUserCmd, conn)) + using (var cmd = new SqlCommand(string.Empty, conn)) { conn.Open(); - cmd.ExecuteNonQuery(); + + foreach (string statement in dropStatements) + { + try + { + cmd.CommandText = statement; + cmd.ExecuteNonQuery(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SqlCredentialTest)}: cleanup statement failed ({statement}): {ex.Message}"); + } + } } } } 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 3701b503a3..8447b17300 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlNotificationTest/SqlNotificationTest.cs @@ -31,7 +31,19 @@ public SqlNotificationTest() _schemaQueue = $"[{_queueName}]"; - Setup(); + // NOTE: If setup fails part way through (for example the queue is created but the service + // is not), this constructor never returns, so xUnit never calls Dispose and the objects + // created so far would be leaked. Their names embed a GUID and xUnit creates one + // instance per test method, so those leaks accumulate quickly. + try + { + Setup(); + } + catch + { + Cleanup(); + throw; + } } public void Dispose() @@ -310,10 +322,13 @@ private static string[] CreateSqlSetupStatements(string tableName, string queueN private static string[] CreateSqlCleanupStatements(string tableName, string queueName, string serviceName) { + // NOTE: Every statement is guarded so that cleanup is idempotent and so that a failure to + // remove one object does not leave the others (in particular the Service Broker queue + // and service) behind in the shared test database. return new string[] { - string.Format("DROP TABLE {0}", tableName), - string.Format("DROP SERVICE [{0}]", serviceName), - string.Format("DROP QUEUE {0}", queueName) + string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) DROP TABLE {0}", tableName), + string.Format("IF EXISTS (SELECT 1 FROM sys.services WHERE name = '{0}') DROP SERVICE [{0}]", serviceName), + string.Format("IF (OBJECT_ID('{0}') IS NOT NULL) DROP QUEUE {0}", queueName) }; } @@ -324,7 +339,19 @@ private void Setup() private void Cleanup() { - RunSQL(CreateSqlCleanupStatements(_tableName, _schemaQueue, _serviceName)); + // Each statement is executed independently: one failure must not prevent the remaining + // objects from being dropped. + foreach (string statement in CreateSqlCleanupStatements(_tableName, _schemaQueue, _serviceName)) + { + try + { + RunSQL(statement); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(SqlNotificationTest)}: cleanup statement failed ({statement}): {ex.Message}"); + } + } } private int RunSQL(params string[] stmts) 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 9aea760e56..91742465e3 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/TransactionTest/TransactionTest.cs @@ -174,11 +174,28 @@ private void DropTempTables() { using (var conn = new SqlConnection(_connectionString)) { - SqlCommand command = new SqlCommand( - string.Format("DROP TABLE [{0}]; DROP TABLE [{1}]", _tempTableName1, _tempTableName2), conn); conn.Open(); + + // NOTE: The drops are guarded and issued separately. Previously they shared a + // single unguarded batch, so a failure to drop the first table also leaked + // the second one. + DropTempTable(conn, _tempTableName1); + DropTempTable(conn, _tempTableName2); + } + } + + private static void DropTempTable(SqlConnection connection, string tableName) + { + try + { + using SqlCommand command = new SqlCommand( + string.Format("IF (OBJECT_ID('[{0}]') IS NOT NULL) DROP TABLE [{0}]", tableName), connection); command.ExecuteNonQuery(); } + catch (Exception ex) + { + Console.WriteLine($"TransactionTest: failed to drop table '{tableName}': {ex.Message}"); + } } public void ResetTables() diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs index 5c9970f6ed..a69a943e3d 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/NativeVectorTestsBase.cs @@ -110,11 +110,16 @@ public NativeVectorTestsBase() _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: $@" + + // NOTE: If this constructor throws, xUnit never calls Dispose, so any object already created + // (each of which has a GUID-based name) would be left in the database permanently. + try + { + _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 @@ -130,6 +135,12 @@ public NativeVectorTestsBase() FROM {_vectorTable.Name} ORDER BY Id DESC; END;"); + } + catch + { + Dispose(); + throw; + } _selectCommand = $"SELECT {VectorColumnName} FROM {_vectorTable.Name} ORDER BY Id DESC"; _insertCommand = $"INSERT INTO {_vectorTable.Name} ({VectorColumnName}) VALUES ({VectorParameterName})"; @@ -150,15 +161,28 @@ protected virtual void Dispose(bool disposing) if (disposing) { - _vectorProcedure?.Dispose(); - _bulkCopySourceTable?.Dispose(); - _vectorTable?.Dispose(); + // Each drop is best-effort: failing to drop one object must not leak the others. + DisposeSafely(_vectorProcedure); + DisposeSafely(_bulkCopySourceTable); + DisposeSafely(_vectorTable); _managementConnection?.Dispose(); } _disposed = true; } + private static void DisposeSafely(IDisposable? disposable) + { + try + { + disposable?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(NativeVectorTestsBase)}: cleanup failed: {ex.Message}"); + } + } + ~NativeVectorTestsBase() => Dispose(false); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs index 1b2d4c792f..03298d3e37 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/VectorTest/VectorBackwardCompatTestBase.cs @@ -47,15 +47,22 @@ protected VectorBackwardCompatTestBase( { Output = output; _connection = new SqlConnection(s_connectionString); - _connection.Open(); - _vectorTable = new Table(_connection, namePrefix + "TestTable", - $"(Id INT PRIMARY KEY IDENTITY, VectorData {columnDefinition} NULL)"); + // NOTE: If this constructor throws, xUnit never calls Dispose, so the connection and the + // objects created so far (each with a GUID-based name) would be left behind permanently. + // Opening the connection is inside the try for the same reason: a failed Open still + // leaves a SqlConnection instance that nothing else will ever dispose. + try + { + _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)"); + _bulkCopySrcTable = new Table(_connection, namePrefix + "BulkCopyTestTable", + "(Id INT PRIMARY KEY IDENTITY, VectorData varchar(max) NULL)"); - string storedProcBody = $@" + string storedProcBody = $@" @InputVectorJson VARCHAR(MAX), -- Input: Serialized float[] as JSON string @OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest inserted row AS @@ -72,7 +79,13 @@ @OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest insert ORDER BY Id DESC; END;"; - _storedProc = new StoredProcedure(_connection, namePrefix + "AsVarcharSp", storedProcBody); + _storedProc = new StoredProcedure(_connection, namePrefix + "AsVarcharSp", storedProcBody); + } + catch + { + Dispose(); + throw; + } _selectCmdString = $"SELECT VectorData FROM {_vectorTable.Name} ORDER BY Id DESC"; _insertCmdString = $"INSERT INTO {_vectorTable.Name} (VectorData) VALUES (@VectorData)"; @@ -80,13 +93,32 @@ @OutputVectorJson VARCHAR(MAX) OUTPUT -- Output: Echoed back from latest insert public void Dispose() { - // RAII objects drop themselves on Dispose in reverse order. - _storedProc?.Dispose(); - _bulkCopySrcTable?.Dispose(); - _vectorTable?.Dispose(); + // RAII objects drop themselves on Dispose in reverse order. Each drop is best-effort so + // that one failure cannot leak the remaining objects. + DisposeSafely(_storedProc); + DisposeSafely(_bulkCopySrcTable); + DisposeSafely(_vectorTable); _connection?.Dispose(); } + // NOTE: This file is not in a nullable annotations context, so the parameter is annotated + // under a scoped `#nullable enable`. The nullability is not incidental: on the + // constructor-failure path Dispose runs with some of these fields still null, so accepting + // null is the contract rather than a defensive afterthought. +#nullable enable + private static void DisposeSafely(IDisposable? disposable) +#nullable restore + { + try + { + disposable?.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"{nameof(VectorBackwardCompatTestBase)}: cleanup failed: {ex.Message}"); + } + } + #region Shared Helpers /// diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Fixtures/AlwaysEncrypted/NativeColumnEncryptionKeyCertificateBaselineFixture.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Fixtures/AlwaysEncrypted/NativeColumnEncryptionKeyCertificateBaselineFixture.cs index d0cfc2a842..8994316343 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Fixtures/AlwaysEncrypted/NativeColumnEncryptionKeyCertificateBaselineFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Fixtures/AlwaysEncrypted/NativeColumnEncryptionKeyCertificateBaselineFixture.cs @@ -51,15 +51,28 @@ public NativeColumnEncryptionKeyCertificateBaselineFixture() : base() { byte[] nativeCertificateBaseline = Resources.AlwaysEncrypted_NativeColumnEncryptionKeyBaseline_Certificate; + + // NOTE: The certificate is deliberately not disposed here. It is owned by the base fixture, which + // removes it from the store on cleanup - and a disposed certificate cannot be matched against a + // store, so disposing it early left it behind in the current user's store permanently. #if NET9_0_OR_GREATER - using X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(nativeCertificateBaseline, NativeCertificatePassword, + X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(nativeCertificateBaseline, NativeCertificatePassword, keyStorageFlags: X509KeyStorageFlags.PersistKeySet); #else - using X509Certificate2 certificate = new(nativeCertificateBaseline, NativeCertificatePassword, + X509Certificate2 certificate = new(nativeCertificateBaseline, NativeCertificatePassword, X509KeyStorageFlags.PersistKeySet); #endif - Thumbprint = certificate.Thumbprint; - AddToStore(certificate, StoreLocation.CurrentUser, StoreName.My); + try + { + Thumbprint = certificate.Thumbprint; + AddToStore(certificate, StoreLocation.CurrentUser, StoreName.My); + } + catch + { + Dispose(); + certificate.Dispose(); + throw; + } } }