Skip to content

Commit 5459c4b

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 463f1f9 commit 5459c4b

30 files changed

+336
-148
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 Task 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: 161 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public static bool TcpConnectionStringDoesNotUseAadAuth
116116
{
117117
get
118118
{
119-
SqlConnectionStringBuilder builder = new (TCPConnectionString);
119+
SqlConnectionStringBuilder builder = new(TCPConnectionString);
120120
return builder.Authentication == SqlAuthenticationMethod.SqlPassword || builder.Authentication == SqlAuthenticationMethod.NotSpecified;
121121
}
122122
}
@@ -548,59 +548,176 @@ public static bool DoesHostAddressContainBothIPv4AndIPv6()
548548
}
549549
}
550550

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

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

603-
return name;
720+
return name.ToString();
604721
}
605722

606723
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
@@ -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)