Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ protected override void CreateObject(string definition)

protected override void DropObject()
{
using SqlCommand dropCommand = new($"IF (OBJECT_ID('{Name}') IS NOT NULL) DROP TYPE {Name}", Connection);
// NOTE: User-defined types live in sys.types, not sys.objects, so OBJECT_ID() always
// returns NULL for them. Using it here silently skipped every drop and leaked the type
// into the (shared) test database. TYPE_ID() is the correct lookup.
using SqlCommand dropCommand = new($"IF (TYPE_ID('{Name}') IS NOT NULL) DROP TYPE {Name}", Connection);

dropCommand.ExecuteNonQuery();
Comment thread
cheenamalhotra marked this conversation as resolved.
Outdated
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,58 +6,88 @@
using System.Collections.Generic;
using System.Data;
using Microsoft.Data.SqlClient.Server;
using Microsoft.Data.SqlClient.Tests.Common.Fixtures.DatabaseObjects;
using Xunit;

namespace Microsoft.Data.SqlClient.ManualTesting.Tests
{
/// <summary>
/// Creates the table type and stored procedure used by <see cref="TvpQueryHintsTests"/> exactly
/// once for the whole test class.
/// </summary>
/// <remarks>
/// Every test in the class needs an identically shaped table type and procedure, so creating them
/// per test only multiplies the amount of DDL issued against the (shared) test database. On Azure
/// SQL Database the resulting schema-modification lock contention was enough to push
/// CREATE/DROP TYPE and CREATE/DROP PROCEDURE past the default 30 second command timeout, which
/// showed up as sporadic "Execution Timeout Expired" failures in the manual test legs. Sharing a
/// single type/procedure via a class fixture cuts the DDL statement count by 5x, and the extended
/// command timeout below absorbs the contention that remains.
/// </remarks>
public sealed class TvpQueryHintsFixture : IDisposable
{
/// <summary>
/// Command timeout (in seconds) applied to every command issued on <see cref="Connection"/>.
/// Deliberately generous: DDL against a shared Azure SQL Database can block for a long time
/// behind concurrently executing test legs.
/// </summary>
private const int CommandTimeoutSeconds = 120;

private readonly UserDefinedType _tableType;
private readonly StoredProcedure _procedure;

public SqlConnection Connection { get; }

public string ProcedureName => _procedure.Name;

public TvpQueryHintsFixture()
{
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString)
{
CommandTimeout = CommandTimeoutSeconds
};

Connection = new SqlConnection(builder.ConnectionString);
Connection.Open();

_tableType = new UserDefinedType(Connection, "QHint",
"TABLE("
+ " c1 Int DEFAULT -1,"
+ " c2 NVarChar(40) DEFAULT N'DEFUALT',"
+ " c3 DateTime DEFAULT '1/1/2006',"
+ " c4 Int DEFAULT -1)");

_procedure = new StoredProcedure(Connection, "QHint_Proc",
$"(@tvp {_tableType.Name} READONLY) AS SELECT TOP(2) * FROM @tvp ORDER BY c1");
}

public void Dispose()
{
_procedure.Dispose();
_tableType.Dispose();
Connection.Dispose();
}
}

/// <summary>
/// Tests for TVP query hints (sort order, uniqueness, default columns).
/// </summary>
[Trait("Set", "3")]
public sealed class TvpQueryHintsTests : IDisposable
public sealed class TvpQueryHintsTests : IClassFixture<TvpQueryHintsFixture>, IDisposable
{
private readonly SqlConnection _conn;
private readonly SqlCommand _cmd;
private readonly SqlParameter _param;
private readonly string _procName;
private readonly string _typeName;

public TvpQueryHintsTests()
public TvpQueryHintsTests(TvpQueryHintsFixture fixture)
{
Guid randomizer = Guid.NewGuid();
_typeName = string.Format("dbo.[QHint_{0}]", randomizer);
_procName = string.Format("dbo.[QHint_Proc_{0}]", randomizer);
string createTypeSql = string.Format(
"CREATE TYPE {0} AS TABLE("
+ " c1 Int DEFAULT -1,"
+ " c2 NVarChar(40) DEFAULT N'DEFUALT',"
+ " c3 DateTime DEFAULT '1/1/2006',"
+ " c4 Int DEFAULT -1)",
_typeName);
string createProcSql = string.Format(
"CREATE PROC {0}(@tvp {1} READONLY) AS SELECT TOP(2) * FROM @tvp ORDER BY c1", _procName, _typeName);

_conn = new SqlConnection(DataTestUtility.TCPConnectionString);
_conn.Open();

_cmd = new SqlCommand(createTypeSql, _conn);
_cmd.ExecuteNonQuery();

_cmd.CommandText = createProcSql;
_cmd.ExecuteNonQuery();

_cmd.CommandText = _procName;
_cmd.CommandType = CommandType.StoredProcedure;
_cmd = new SqlCommand(fixture.ProcedureName, fixture.Connection)
{
CommandType = CommandType.StoredProcedure
};
_param = _cmd.Parameters.Add("@tvp", SqlDbType.Structured);
}

public void Dispose()
{
string dropSql = string.Format("DROP PROC {0}; DROP TYPE {1}", _procName, _typeName);
using SqlCommand cmd = new(dropSql, _conn);
cmd.ExecuteNonQuery();
_conn.Dispose();
}
public void Dispose() => _cmd.Dispose();

[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
public void SortOrderSimple()
Expand Down
Loading