Skip to content

Commit 1e3557c

Browse files
committed
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. - Added link to database identifier syntax.
1 parent cc6cd33 commit 1e3557c

27 files changed

+477
-153
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
@@ -148,8 +148,8 @@ public void SqlParameterProperties(string connection)
148148
const string firstColumnName = @"firstColumn";
149149
const string secondColumnName = @"secondColumn";
150150
const string thirdColumnName = @"thirdColumn";
151-
string inputProcedureName = DataTestUtility.GetUniqueName("InputProc").ToString();
152-
string outputProcedureName = DataTestUtility.GetUniqueName("OutputProc").ToString();
151+
string inputProcedureName = DataTestUtility.GetShortName("InputProc").ToString();
152+
string outputProcedureName = DataTestUtility.GetShortName("OutputProc").ToString();
153153
const int charColumnSize = 100;
154154
const int decimalColumnPrecision = 10;
155155
const int decimalColumnScale = 4;
@@ -694,7 +694,7 @@ public void TestExecuteReader(string connection)
694694
[ClassData(typeof(AEConnectionStringProvider))]
695695
public async void TestExecuteReaderAsyncWithLargeQuery(string connectionString)
696696
{
697-
string randomName = DataTestUtility.GetUniqueName(Guid.NewGuid().ToString().Replace("-", ""), false);
697+
string randomName = DataTestUtility.GetShortName(Guid.NewGuid().ToString().Replace("-", ""), false);
698698
if (randomName.Length > 50)
699699
{
700700
randomName = randomName.Substring(0, 50);
@@ -878,8 +878,8 @@ public void TestEnclaveStoredProceduresWithAndWithoutParameters(string connectio
878878
using SqlCommand sqlCommand = new("", sqlConnection, transaction: null,
879879
columnEncryptionSetting: SqlCommandColumnEncryptionSetting.Enabled);
880880

881-
string procWithoutParams = DataTestUtility.GetUniqueName("EnclaveWithoutParams", withBracket: false);
882-
string procWithParam = DataTestUtility.GetUniqueName("EnclaveWithParams", withBracket: false);
881+
string procWithoutParams = DataTestUtility.GetShortName("EnclaveWithoutParams", withBracket: false);
882+
string procWithParam = DataTestUtility.GetShortName("EnclaveWithParams", withBracket: false);
883883

884884
try
885885
{

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,9 @@ public void TestRoundTripWithCSPAndCertStoreProvider()
162162
public void TestEncryptDecryptWithCSP(string connectionString)
163163
{
164164
string providerName = @"Microsoft Enhanced RSA and AES Cryptographic Provider";
165-
string keyIdentifier = DataTestUtility.GetUniqueNameForSqlServer("CSP");
165+
string keyIdentifier = DataTestUtility.GetLongName("CSP");
166+
CspParameters namedCspParameters = new CspParameters(providerType, providerName, keyIdentifier);
167+
using SQLSetupStrategyCspProvider sqlSetupStrategyCsp = new SQLSetupStrategyCspProvider(namedCspParameters);
166168

167169
try
168170
{

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

Lines changed: 163 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth
117117
{
118118
get
119119
{
120-
SqlConnectionStringBuilder builder = new (TCPConnectionString);
120+
SqlConnectionStringBuilder builder = new(TCPConnectionString);
121121
return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified;
122122
}
123123
}
@@ -546,59 +546,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6()
546546
}
547547
}
548548

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

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

601-
return name;
718+
return name.ToString();
602719
}
603720

604721
public static void DropTable(SqlConnection sqlConnection, string tableName)

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
@@ -383,7 +383,7 @@ public static void ConnectionOpenDisableRetry()
383383
Assert.Throws<SqlException>(() => sqlConnection.Open());
384384
timer.Stop();
385385
duration = timer.Elapsed;
386-
Assert.True(duration.Seconds > 5, $"Connection Open() with retries took less time than expected. Expect > 5 sec with transient fault handling. Took {duration.Seconds} sec."); // sqlConnection.Open();
386+
Assert.True(duration.Seconds > 5, $"Connection Open() with retries took less time than expected. Expect > 5 sec with transient fault handling. Took {duration.Seconds} sec."); // sqlConnection.Open();
387387
}
388388

389389
[PlatformSpecific(TestPlatforms.Windows)]

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
@@ -133,7 +133,7 @@ public static void CheckSparseColumnBit()
133133
[InlineData("Georgian_Modern_Sort_CI_AS")]
134134
public static void CollatedDataReaderTest(string collation)
135135
{
136-
string dbName = DataTestUtility.GetUniqueName("CollationTest", false);
136+
string dbName = DataTestUtility.GetShortName("CollationTest", false);
137137

138138
SqlConnectionStringBuilder builder = new(DataTestUtility.TCPConnectionString)
139139
{

0 commit comments

Comments
 (0)