|
6 | 6 | using System.Collections.Generic; |
7 | 7 | using System.Data; |
8 | 8 | using Microsoft.Data.SqlClient.Server; |
| 9 | +using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects; |
9 | 10 | using Xunit; |
10 | 11 |
|
11 | 12 | namespace Microsoft.Data.SqlClient.ManualTesting.Tests |
12 | 13 | { |
13 | 14 | /// <summary> |
14 | | - /// Tests for TVP query hints (sort order, uniqueness, default columns). |
| 15 | + /// Creates the table type and stored procedure used by <see cref="TvpQueryHintsTests"/> exactly |
| 16 | + /// once for the whole test class. |
15 | 17 | /// </summary> |
16 | | - [Trait("Set", "3")] |
17 | | - public sealed class TvpQueryHintsTests : IDisposable |
| 18 | + /// <remarks> |
| 19 | + /// Every test in the class needs an identically shaped table type and procedure, so creating them |
| 20 | + /// per test only multiplies the amount of DDL issued against the (shared) test database. On Azure |
| 21 | + /// SQL Database the resulting schema-modification lock contention was enough to push |
| 22 | + /// CREATE/DROP TYPE and CREATE/DROP PROCEDURE past the default 30 second command timeout, which |
| 23 | + /// showed up as sporadic "Execution Timeout Expired" failures in the manual test legs. Sharing a |
| 24 | + /// single type/procedure via a class fixture cuts the DDL statement count by 5x, and the extended |
| 25 | + /// command timeout below absorbs the contention that remains. |
| 26 | + /// </remarks> |
| 27 | + public sealed class TvpQueryHintsFixture : IDisposable |
18 | 28 | { |
19 | | - private readonly SqlConnection _conn; |
20 | | - private readonly SqlCommand _cmd; |
21 | | - private readonly SqlParameter _param; |
22 | | - private readonly string _procName; |
23 | | - private readonly string _typeName; |
| 29 | + /// <summary> |
| 30 | + /// Command timeout (in seconds) applied to every command issued on <see cref="Connection"/>. |
| 31 | + /// Deliberately generous: DDL against a shared Azure SQL Database can block for a long time |
| 32 | + /// behind concurrently executing test legs. |
| 33 | + /// </summary> |
| 34 | + private const int CommandTimeoutSeconds = 120; |
| 35 | + |
| 36 | + private readonly UserDefinedType _tableType; |
| 37 | + private readonly StoredProcedure _procedure; |
24 | 38 |
|
25 | | - public TvpQueryHintsTests() |
| 39 | + public SqlConnection Connection { get; } |
| 40 | + |
| 41 | + public string ProcedureName => _procedure.Name; |
| 42 | + |
| 43 | + public TvpQueryHintsFixture() |
26 | 44 | { |
27 | | - Guid randomizer = Guid.NewGuid(); |
28 | | - _typeName = string.Format("dbo.[QHint_{0}]", randomizer); |
29 | | - _procName = string.Format("dbo.[QHint_Proc_{0}]", randomizer); |
30 | | - string createTypeSql = string.Format( |
31 | | - "CREATE TYPE {0} AS TABLE(" |
| 45 | + SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString) |
| 46 | + { |
| 47 | + CommandTimeout = CommandTimeoutSeconds |
| 48 | + }; |
| 49 | + |
| 50 | + Connection = new SqlConnection(builder.ConnectionString); |
| 51 | + |
| 52 | + // Partial construction must not leak the objects created so far, otherwise a transient |
| 53 | + // failure here would orphan a type in the shared database. |
| 54 | + try |
| 55 | + { |
| 56 | + Connection.Open(); |
| 57 | + |
| 58 | + _tableType = new UserDefinedType(Connection, "QHint", |
| 59 | + "TABLE(" |
32 | 60 | + " c1 Int DEFAULT -1," |
33 | 61 | + " c2 NVarChar(40) DEFAULT N'DEFUALT'," |
34 | 62 | + " c3 DateTime DEFAULT '1/1/2006'," |
35 | | - + " c4 Int DEFAULT -1)", |
36 | | - _typeName); |
37 | | - string createProcSql = string.Format( |
38 | | - "CREATE PROC {0}(@tvp {1} READONLY) AS SELECT TOP(2) * FROM @tvp ORDER BY c1", _procName, _typeName); |
39 | | - |
40 | | - _conn = new SqlConnection(DataTestUtility.TCPConnectionString); |
41 | | - _conn.Open(); |
| 63 | + + " c4 Int DEFAULT -1)"); |
| 64 | + |
| 65 | + try |
| 66 | + { |
| 67 | + _procedure = new StoredProcedure(Connection, "QHint_Proc", |
| 68 | + $"(@tvp {_tableType.Name} READONLY) AS SELECT TOP(2) * FROM @tvp ORDER BY c1"); |
| 69 | + } |
| 70 | + catch |
| 71 | + { |
| 72 | + _tableType.Dispose(); |
| 73 | + throw; |
| 74 | + } |
| 75 | + } |
| 76 | + catch |
| 77 | + { |
| 78 | + Connection.Dispose(); |
| 79 | + throw; |
| 80 | + } |
| 81 | + } |
42 | 82 |
|
43 | | - _cmd = new SqlCommand(createTypeSql, _conn); |
44 | | - _cmd.ExecuteNonQuery(); |
| 83 | + public void Dispose() |
| 84 | + { |
| 85 | + // Each step runs even if an earlier one fails, so a transient error while dropping the |
| 86 | + // procedure cannot leave the type (or the connection) behind. |
| 87 | + try |
| 88 | + { |
| 89 | + _procedure.Dispose(); |
| 90 | + } |
| 91 | + finally |
| 92 | + { |
| 93 | + try |
| 94 | + { |
| 95 | + _tableType.Dispose(); |
| 96 | + } |
| 97 | + finally |
| 98 | + { |
| 99 | + Connection.Dispose(); |
| 100 | + } |
| 101 | + } |
| 102 | + } |
| 103 | + } |
45 | 104 |
|
46 | | - _cmd.CommandText = createProcSql; |
47 | | - _cmd.ExecuteNonQuery(); |
| 105 | + /// <summary> |
| 106 | + /// Tests for TVP query hints (sort order, uniqueness, default columns). |
| 107 | + /// </summary> |
| 108 | + [Trait("Set", "3")] |
| 109 | + public sealed class TvpQueryHintsTests : IClassFixture<TvpQueryHintsFixture>, IDisposable |
| 110 | + { |
| 111 | + private readonly SqlCommand _cmd; |
| 112 | + private readonly SqlParameter _param; |
48 | 113 |
|
49 | | - _cmd.CommandText = _procName; |
50 | | - _cmd.CommandType = CommandType.StoredProcedure; |
| 114 | + public TvpQueryHintsTests(TvpQueryHintsFixture fixture) |
| 115 | + { |
| 116 | + _cmd = new SqlCommand(fixture.ProcedureName, fixture.Connection) |
| 117 | + { |
| 118 | + CommandType = CommandType.StoredProcedure |
| 119 | + }; |
51 | 120 | _param = _cmd.Parameters.Add("@tvp", SqlDbType.Structured); |
52 | 121 | } |
53 | 122 |
|
54 | | - public void Dispose() |
55 | | - { |
56 | | - string dropSql = string.Format("DROP PROC {0}; DROP TYPE {1}", _procName, _typeName); |
57 | | - using SqlCommand cmd = new(dropSql, _conn); |
58 | | - cmd.ExecuteNonQuery(); |
59 | | - _conn.Dispose(); |
60 | | - } |
| 123 | + public void Dispose() => _cmd.Dispose(); |
61 | 124 |
|
62 | 125 | [ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))] |
63 | 126 | public void SortOrderSimple() |
|
0 commit comments