Skip to content

Commit 1297673

Browse files
Fix unique db object name issues (#3597)
* User Story 38481: Fix unique db object name issues - Fixed the unique name generators to: - Keep max lengths to 30 and 96 characters respectively. - Ensure uniqueness at the start of the names. * User Story 38481: Fix unique db object name issues - Increased long name suffix to 32 characters. * Update src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs Co-authored-by: Copilot <[email protected]> * User Story 38481: Fix unique db object name issues - Replaced range operators with Substring() since .NET Framework doesn't have ranges. * User Story 38481: Fix unique db object name issues - Replaced '-' with '_' to avoid T-SQL syntax issues. * User Story 38481: Fix unique db object name issues - Swapped the suffix into a prefix. - Added debugging to see what the names look like when failures occur. * User Story 38481: Fix unique db object name issues - Removed debugging console emission, since it breaks fragile tests! - Added link to database identifier syntax. --------- Co-authored-by: Copilot <[email protected]>
1 parent 8b58d50 commit 1297673

33 files changed

+276
-159
lines changed

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/ApiShould.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,8 @@ public void SqlParameterProperties(string connection)
166166
const string firstColumnName = @"firstColumn";
167167
const string secondColumnName = @"secondColumn";
168168
const string thirdColumnName = @"thirdColumn";
169-
string inputProcedureName = DataTestUtility.GetUniqueName("InputProc").ToString();
170-
string outputProcedureName = DataTestUtility.GetUniqueName("OutputProc").ToString();
169+
string inputProcedureName = DataTestUtility.GetShortName("InputProc").ToString();
170+
string outputProcedureName = DataTestUtility.GetShortName("OutputProc").ToString();
171171
const int charColumnSize = 100;
172172
const int decimalColumnPrecision = 10;
173173
const int decimalColumnScale = 4;
@@ -722,7 +722,7 @@ public void TestExecuteReader(string connection)
722722
[ClassData(typeof(AEConnectionStringProvider))]
723723
public async Task TestExecuteReaderAsyncWithLargeQuery(string connectionString)
724724
{
725-
string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false);
725+
string randomName = DataTestUtility.GetShortName(Guid.NewGuid().ToString().Replace("-", ""), false);
726726
if (randomName.Length > 50)
727727
{
728728
randomName = randomName.Substring(0, 50);
@@ -912,8 +912,8 @@ public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectio
912912
using SqlCommand sqlCommand = new("", sqlConnection, transaction: null,
913913
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled);
914914

915-
string procWithoutParams = DataTestUtility.GetUniqueName("EnclaveWithoutParams", withBracket: false);
916-
string procWithParam = DataTestUtility.GetUniqueName("EnclaveWithParams", withBracket: false);
915+
string procWithoutParams = DataTestUtility.GetShortName("EnclaveWithoutParams", withBracket: false);
916+
string procWithParam = DataTestUtility.GetShortName("EnclaveWithParams", withBracket: false);
917917

918918
try
919919
{

src/Microsoft.Data.SqlClient/tests/ManualTests/AlwaysEncrypted/CspProviderExt.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public void TestRoundTripWithCspAndCertStoreProvider()
5454
[MemberData(nameof(TestEncryptDecryptWithCsp_Data))]
5555
public void TestEncryptDecryptWithCsp(string connectionString, string providerName, int providerType)
5656
{
57-
string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP");
57+
string keyIdentifier = DataTestUtility.GetLongName("CSP");
5858
CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier);
5959
using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters);
6060

src/Microsoft.Data.SqlClient/tests/ManualTests/DataCommon/DataTestUtility.cs

Lines changed: 163 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth
111111
{
112112
get
113113
{
114-
SqlConnectionStringBuilder builder = new (TCPConnectionString);
114+
SqlConnectionStringBuilder builder = new(TCPConnectionString);
115115
return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified;
116116
}
117117
}
@@ -556,59 +556,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6()
556556
}
557557
}
558558

559+
// Generate a new GUID and return the characters from its 1st and 4th
560+
// parts, as shown here:
561+
//
562+
// 7ff01cb8-88c7-11f0-b433-00155d7e531e
563+
// ^^^^^^^^ ^^^^
564+
//
565+
// These 12 characters are concatenated together without any
566+
// separators. These 2 parts typically comprise a timestamp and clock
567+
// sequence, most likely to be unique for tests that generate names in
568+
// quick succession.
569+
private static string GetGuidParts()
570+
{
571+
var guid = Guid.NewGuid().ToString();
572+
// GOTCHA: The slice operator is inclusive of the start index and
573+
// exclusive of the end index!
574+
return guid.Substring(0, 8) + guid.Substring(19, 4);
575+
}
576+
559577
/// <summary>
560-
/// Generate a unique name to use in Sql Server;
561-
/// some providers does not support names (Oracle supports up to 30).
578+
/// Generate a short unique database object name, whose maximum length
579+
/// is 30 characters, with the format:
580+
///
581+
/// <Prefix>_<GuidParts>
582+
///
583+
/// The Prefix will be truncated to satisfy the overall maximum length.
584+
///
585+
/// The GUID parts will be the characters from the 1st and 4th blocks
586+
/// from a traditional string representation, as shown here:
587+
///
588+
/// 7ff01cb8-88c7-11f0-b433-00155d7e531e
589+
/// ^^^^^^^^ ^^^^
590+
///
591+
/// These 2 parts typically comprise a timestamp and clock sequence,
592+
/// most likely to be unique for tests that generate names in quick
593+
/// succession. The 12 characters are concatenated together without any
594+
/// separators.
562595
/// </summary>
563-
/// <param name="prefix">The name length will be no more then (16 + prefix.Length + escapeLeft.Length + escapeRight.Length).</param>
564-
/// <param name="withBracket">Name without brackets.</param>
565-
/// <returns>Unique name by considering the Sql Server naming rules.</returns>
566-
public static string GetUniqueName(string prefix, bool withBracket = true)
567-
{
568-
string escapeLeft = withBracket ? "[" : string.Empty;
569-
string escapeRight = withBracket ? "]" : string.Empty;
570-
string uniqueName = string.Format("{0}{1}_{2}_{3}{4}",
571-
escapeLeft,
572-
prefix,
573-
DateTime.Now.Ticks.ToString("X", CultureInfo.InvariantCulture), // up to 8 characters
574-
Guid.NewGuid().ToString().Substring(0, 6), // take the first 6 characters only
575-
escapeRight);
576-
return uniqueName;
596+
///
597+
/// <param name="prefix">
598+
/// The prefix to use when generating the unique name, truncated to at
599+
/// most 18 characters when withBracket is false, and 16 characters when
600+
/// withBracket is true.
601+
///
602+
/// This should not contain any characters that cannot be used in
603+
/// database object names. See:
604+
///
605+
/// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
606+
/// </param>
607+
///
608+
/// <param name="withBracket">
609+
/// When true, the entire generated name will be enclosed in square
610+
/// brackets, for example:
611+
///
612+
/// [MyPrefix_7ff01cb811f0]
613+
/// </param>
614+
///
615+
/// <returns>
616+
/// A unique database object name, no more than 30 characters long.
617+
/// </returns>
618+
public static string GetShortName(string prefix, bool withBracket = true)
619+
{
620+
StringBuilder name = new(30);
621+
622+
if (withBracket)
623+
{
624+
name.Append('[');
625+
}
626+
627+
int maxPrefixLength = withBracket ? 16 : 18;
628+
if (prefix.Length > maxPrefixLength)
629+
{
630+
prefix = prefix.Substring(0, maxPrefixLength);
631+
}
632+
633+
name.Append(prefix);
634+
name.Append('_');
635+
name.Append(GetGuidParts());
636+
637+
if (withBracket)
638+
{
639+
name.Append(']');
640+
}
641+
642+
return name.ToString();
577643
}
578644

579645
/// <summary>
580-
/// Uses environment values `UserName` and `MachineName` in addition to the specified `prefix` and current date
581-
/// to generate a unique name to use in Sql Server;
582-
/// SQL Server supports long names (up to 128 characters), add extra info for troubleshooting.
646+
/// Generate a long unique database object name, whose maximum length is
647+
/// 96 characters, with the format:
648+
///
649+
/// <Prefix>_<GuidParts>_<UserName>_<MachineName>
650+
///
651+
/// The Prefix will be truncated to satisfy the overall maximum length.
652+
///
653+
/// The GUID Parts will be the characters from the 1st and 4th blocks
654+
/// from a traditional string representation, as shown here:
655+
///
656+
/// 7ff01cb8-88c7-11f0-b433-00155d7e531e
657+
/// ^^^^^^^^ ^^^^
658+
///
659+
/// These 2 parts typically comprise a timestamp and clock sequence,
660+
/// most likely to be unique for tests that generate names in quick
661+
/// succession. The 12 characters are concatenated together without any
662+
/// separators.
663+
///
664+
/// The UserName and MachineName are obtained from the Environment,
665+
/// and will be truncated to satisfy the maximum overall length.
583666
/// </summary>
584-
/// <param name="prefix">Add the prefix to the generate string.</param>
585-
/// <param name="withBracket">Database name must be pass with brackets by default.</param>
586-
/// <returns>Unique name by considering the Sql Server naming rules, never longer than 96 characters.</returns>
587-
public static string GetUniqueNameForSqlServer(string prefix, bool withBracket = true)
588-
{
589-
string extendedPrefix = string.Format(
590-
"{0}_{1}_{2}@{3}",
591-
prefix,
592-
Environment.UserName,
593-
Environment.MachineName,
594-
DateTime.Now.ToString("yyyy_MM_dd", CultureInfo.InvariantCulture));
595-
string name = GetUniqueName(extendedPrefix, withBracket);
596-
597-
// Truncate to no more than 96 characters.
598-
const int maxLen = 96;
599-
if (name.Length > maxLen)
600-
{
601-
if (withBracket)
602-
{
603-
name = name.Substring(0, maxLen - 1) + ']';
604-
}
605-
else
606-
{
607-
name = name.Substring(0, maxLen);
608-
}
667+
///
668+
/// <param name="prefix">
669+
/// The prefix to use when generating the unique name, truncated to at
670+
/// most 32 characters.
671+
///
672+
/// This should not contain any characters that cannot be used in
673+
/// database object names. See:
674+
///
675+
/// https://learn.microsoft.com/en-us/sql/relational-databases/databases/database-identifiers?view=sql-server-ver17#rules-for-regular-identifiers
676+
/// </param>
677+
///
678+
/// <param name="withBracket">
679+
/// When true, the entire generated name will be enclosed in square
680+
/// brackets, for example:
681+
///
682+
/// [MyPrefix_7ff01cb811f0_test_user_ci_agent_machine_name]
683+
/// </param>
684+
///
685+
/// <returns>
686+
/// A unique database object name, no more than 96 characters long.
687+
/// </returns>
688+
public static string GetLongName(string prefix, bool withBracket = true)
689+
{
690+
StringBuilder name = new(96);
691+
692+
if (withBracket)
693+
{
694+
name.Append('[');
695+
}
696+
697+
if (prefix.Length > 32)
698+
{
699+
prefix = prefix.Substring(0, 32);
700+
}
701+
702+
name.Append(prefix);
703+
name.Append('_');
704+
name.Append(GetGuidParts());
705+
name.Append('_');
706+
707+
var suffix =
708+
Environment.UserName + '_' +
709+
Environment.MachineName;
710+
711+
int maxSuffixLength = 96 - name.Length;
712+
if (withBracket)
713+
{
714+
--maxSuffixLength;
715+
}
716+
if (suffix.Length > maxSuffixLength)
717+
{
718+
suffix = suffix.Substring(0, maxSuffixLength);
719+
}
720+
721+
name.Append(suffix);
722+
723+
if (withBracket)
724+
{
725+
name.Append(']');
609726
}
610727

611-
return name;
728+
return name.ToString();
612729
}
613730

614731
public static void CreateTable(SqlConnection sqlConnection, string tableName, string createBody)

src/Microsoft.Data.SqlClient/tests/ManualTests/ProviderAgnostic/ReaderTest/ReaderTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public static void TestMain()
1919
{
2020
string connectionString = DataTestUtility.TCPConnectionString;
2121

22-
string tempTable = DataTestUtility.GetUniqueNameForSqlServer("table");
22+
string tempTable = DataTestUtility.GetLongName("table");
2323

2424
DbProviderFactory provider = SqlClientFactory.Instance;
2525
try
@@ -275,7 +275,7 @@ public static void TestMain()
275275
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
276276
public static void SqlDataReader_SqlBuffer_GetFieldValue()
277277
{
278-
string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue");
278+
string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue");
279279
DateTimeOffset dtoffset = DateTimeOffset.Now;
280280
DateTime dt = DateTime.Now;
281281
//Exclude the millisecond because of rounding at some points by SQL Server.
@@ -374,7 +374,7 @@ public static void SqlDataReader_SqlBuffer_GetFieldValue()
374374
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
375375
public static async Task SqlDataReader_SqlBuffer_GetFieldValue_Async()
376376
{
377-
string tableName = DataTestUtility.GetUniqueNameForSqlServer("SqlBuffer_GetFieldValue_Async");
377+
string tableName = DataTestUtility.GetLongName("SqlBuffer_GetFieldValue_Async");
378378
DateTimeOffset dtoffset = DateTimeOffset.Now;
379379
DateTime dt = DateTime.Now;
380380
//Exclude the millisecond because of rounding at some points by SQL Server.

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/AdapterTest/AdapterTest.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public class AdapterTest
5454
public AdapterTest()
5555
{
5656
// create random name for temp tables
57-
_tempTable = DataTestUtility.GetUniqueName("AdapterTest");
57+
_tempTable = DataTestUtility.GetShortName("AdapterTest");
5858
_tempTable = _tempTable.Replace('-', '_');
5959

6060
_randomGuid = Guid.NewGuid().ToString();
@@ -555,7 +555,7 @@ public void ParameterTest_AllTypes()
555555
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
556556
public void ParameterTest_InOut()
557557
{
558-
string procName = DataTestUtility.GetUniqueName("P");
558+
string procName = DataTestUtility.GetShortName("P");
559559
// input, output
560560
string spCreateInOut =
561561
"CREATE PROCEDURE " + procName + " @in int, @inout int OUTPUT, @out nvarchar(8) OUTPUT " +
@@ -836,13 +836,13 @@ public void BulkUpdateTest()
836836
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
837837
public void UpdateRefreshTest()
838838
{
839-
string identTableName = DataTestUtility.GetUniqueName("ID_");
839+
string identTableName = DataTestUtility.GetShortName("ID_");
840840
string createIdentTable =
841841
$"CREATE TABLE {identTableName} (id int IDENTITY," +
842842
"LastName nvarchar(50) NULL," +
843843
"Firstname nvarchar(50) NULL)";
844844

845-
string spName = DataTestUtility.GetUniqueName("sp_insert", withBracket: false);
845+
string spName = DataTestUtility.GetShortName("sp_insert", withBracket: false);
846846
string spCreateInsert =
847847
$"CREATE PROCEDURE {spName}" +
848848
"(@FirstName nvarchar(50), @LastName nvarchar(50), @id int OUTPUT) " +
@@ -1155,7 +1155,7 @@ public void AutoGenUpdateTest()
11551155
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse))]
11561156
public void AutoGenErrorTest()
11571157
{
1158-
string identTableName = DataTestUtility.GetUniqueName("ID_");
1158+
string identTableName = DataTestUtility.GetShortName("ID_");
11591159
string createIdentTable =
11601160
$"CREATE TABLE {identTableName} (id int IDENTITY," +
11611161
"LastName nvarchar(50) NULL," +

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ConnectivityTests/ConnectivityTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ public static async Task ConnectionOpenAsyncDisableRetry()
393393
{
394394
SqlConnectionStringBuilder connectionStringBuilder = new(DataTestUtility.TCPConnectionString)
395395
{
396-
InitialCatalog = DataTestUtility.GetUniqueNameForSqlServer("DoesNotExist", false),
396+
InitialCatalog = DataTestUtility.GetLongName("DoesNotExist", false),
397397
Pooling = false,
398398
ConnectTimeout = 15,
399399
ConnectRetryCount = 3

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataClassificationTest/DataClassificationTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public static class DataClassificationTest
1818
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsNotAzureSynapse), nameof(DataTestUtility.IsSupportedDataClassification))]
1919
public static void TestDataClassificationResultSetRank()
2020
{
21-
s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
21+
s_tableName = DataTestUtility.GetLongName("DC");
2222
using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString))
2323
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
2424
{
@@ -41,7 +41,7 @@ public static void TestDataClassificationResultSetRank()
4141
[ConditionalFact(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup), nameof(DataTestUtility.IsSupportedDataClassification))]
4242
public static void TestDataClassificationResultSet()
4343
{
44-
s_tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
44+
s_tableName = DataTestUtility.GetLongName("DC");
4545
using (SqlConnection sqlConnection = new SqlConnection(DataTestUtility.TCPConnectionString))
4646
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
4747
{
@@ -232,7 +232,7 @@ public static void TestDataClassificationBulkCopy()
232232
data.Rows.Add(Guid.NewGuid(), "Company 2", "[email protected]", 1);
233233
data.Rows.Add(Guid.NewGuid(), "Company 3", "[email protected]", 1);
234234

235-
var tableName = DataTestUtility.GetUniqueNameForSqlServer("DC");
235+
var tableName = DataTestUtility.GetLongName("DC");
236236

237237
using (var connection = new SqlConnection(DataTestUtility.TCPConnectionString))
238238
{

src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public static void CheckSparseColumnBit()
131131
[InlineData("Georgian_Modern_Sort_CI_AS")]
132132
public static void CollatedDataReaderTest(string collation)
133133
{
134-
string dbName = DataTestUtility.GetUniqueName("CollationTest", false);
134+
string dbName = DataTestUtility.GetShortName("CollationTest", false);
135135

136136
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString)
137137
{

0 commit comments

Comments
 (0)