Skip to content

Commit 91d2a62

Browse files
committed
Added pattern-based implementations of all collations shipped with SQL Server 2025.
1 parent 09c7fca commit 91d2a62

17 files changed

Lines changed: 1759 additions & 717 deletions

SqlServerSimulator.Tests.Internal/CollationTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ namespace SqlServerSimulator;
55
/// <summary>
66
/// Algorithm-contract tests for the <see cref="Collation"/> implementations
77
/// that aren't routed through public SQL today. The default collation's
8-
/// behavior (<see cref="Collation.Default"/>) and the recognized-name
9-
/// surface (<see cref="Collation.Recognized"/> /
10-
/// <see cref="Collation.ByName"/>) are exercised in the public
8+
/// behavior (<see cref="Collation.Default"/>) and the parser-driven
9+
/// name-resolution surface (<see cref="Collation.TryGet"/> /
10+
/// <see cref="Collation.IsRecognized"/>) are exercised in the public
1111
/// <c>CollationBehaviorTests</c> / <c>CollationMetadataTests</c> /
1212
/// <c>LikeTests</c>; this file keeps only the dormant comparers
1313
/// (<see cref="Collation.Latin1General100CiAs"/>,

SqlServerSimulator.Tests/AggregateTests.cs

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -413,20 +413,17 @@ public void Max_BitColumn_RaisesMsg8117()
413413
"Operand data type bit is invalid for max operator.");
414414

415415
/// <summary>
416-
/// Under <c>SQL_Latin1_General_CP1_CI_AS</c> (the simulator's default),
417-
/// apostrophe is meaningful in sort — its CP1252 code point 0x27 sorts
418-
/// before all letters, so MIN of ('Aaronsburg', "'Aiea") is "'Aiea".
419-
/// The Windows-style primary-weight-zero treatment that would push
420-
/// "'Aiea" alongside "A…" entries is exclusive to
421-
/// <c>Latin1_General_100_CI_AS</c> / <c>Latin1_General_CI_AS</c> — see
422-
/// <c>SqlServerSimulator.Tests.Internal/CollationTests.cs</c> for the
423-
/// direct exercise of that algorithm. Data routing still goes through
424-
/// the database default regardless of declared column collation (see
425-
/// <c>docs/claude/database-options.md</c>).
416+
/// Probe-confirmed against SQL Server 2025: every recognized collation
417+
/// (SQL_*, Windows-style, locale) applies <c>IgnoreSymbols</c> in sort
418+
/// — apostrophe / hyphen drop out of the primary sort key, so MIN of
419+
/// ('Aaronsburg', "'Aiea") returns 'Aaronsburg' because apostrophe is
420+
/// stripped from "'Aiea" leaving "Aiea" > "Aaronsburg". Equality keeps
421+
/// symbols significant; <see cref="Equality_String_DistinguishesApostrophe"/>
422+
/// pins that asymmetry.
426423
/// </summary>
427424
[TestMethod]
428-
public void Min_String_SqlCollation_ApostropheSortsBeforeLetters()
429-
=> AreEqual("'Aiea", new Simulation().ExecuteScalar(
425+
public void Min_String_DefaultCollation_ApostropheIsIgnoredInSort()
426+
=> AreEqual("Aaronsburg", new Simulation().ExecuteScalar(
430427
"create table t (s nvarchar(40) not null); insert t values (N'Aaronsburg'), (N'''Aiea'); select min(s) from t"));
431428

432429
/// <summary>

SqlServerSimulator.Tests/AlterDatabaseOptionsTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,20 @@ public void Collate_Default_Accepts()
9898
=> AreEqual(-1, new Simulation().ExecuteNonQuery(
9999
"ALTER DATABASE claude COLLATE SQL_Latin1_General_CP1_CI_AS"));
100100

101+
/// <summary>
102+
/// Names outside the catalog raise <c>NotSupportedException</c> with the
103+
/// "recognized list" wording. Real SQL Server raises Msg 448; the
104+
/// simulator's "honest about what's modeled" stance keeps the
105+
/// distinction visible. The chosen name <c>MadeUp_Locale_CI_AS</c> has
106+
/// a parseable grammar but isn't in the per-prefix tail-set catalog
107+
/// (probed against SQL Server 2025).
108+
/// </summary>
101109
[TestMethod]
102110
public void Collate_NonDefault_RaisesNotSupported()
103111
{
104112
var ex = Throws<NotSupportedException>(() =>
105-
new Simulation().ExecuteNonQuery("ALTER DATABASE claude COLLATE Japanese_CI_AS"));
106-
Contains("Japanese_CI_AS", ex.Message);
113+
new Simulation().ExecuteNonQuery("ALTER DATABASE claude COLLATE MadeUp_Locale_CI_AS"));
114+
Contains("MadeUp_Locale_CI_AS", ex.Message);
107115
Contains("recognized list", ex.Message);
108116
}
109117

SqlServerSimulator.Tests/CollationBehaviorTests.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,14 @@ public void DefaultCollation_OrderBy_AsciiLowerVsUpper_IsCaseInsensitive()
6262
}
6363

6464
/// <summary>
65-
/// Apostrophe is significant in the default collation's sort key, so
66-
/// <c>'Aaronsburg' &lt; '''Aiea'</c> — apostrophe (0x27) sorts before
67-
/// 'a' (0x61). Distinct from the Windows-100 CI_AS variant where the
68-
/// apostrophe drops out of the primary sort key.
65+
/// Probe-confirmed against SQL Server 2025: the default collation
66+
/// applies <c>IgnoreSymbols</c> in sort — apostrophe drops out of the
67+
/// primary sort key, so "'Aiea" sorts as "Aiea" which is greater than
68+
/// "Aaronsburg". Equality keeps symbols significant — the direct
69+
/// asymmetry probe lives in <c>CollationTests</c>.
6970
/// </summary>
7071
[TestMethod]
71-
public void DefaultCollation_OrderBy_ApostropheIsSignificant()
72+
public void DefaultCollation_OrderBy_ApostropheIsIgnored()
7273
{
7374
var sim = new Simulation();
7475
_ = sim.ExecuteNonQuery(
@@ -77,7 +78,7 @@ public void DefaultCollation_OrderBy_ApostropheIsSignificant()
7778
var rows = new List<string>();
7879
while (reader.Read())
7980
rows.Add(reader.GetString(0));
80-
CollectionAssert.AreEqual(new[] { "'Aiea", "Aaronsburg" }, rows);
81+
CollectionAssert.AreEqual(new[] { "Aaronsburg", "'Aiea" }, rows);
8182
}
8283

8384
/// <summary>

SqlServerSimulator.Tests/CollationDeclaredColumnTests.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,16 +369,19 @@ insert dflt values (N'istanbul')
369369

370370
/// <summary>
371371
/// Unknown collation name raises a clear NotSupportedException (it's
372-
/// not on the simulator's recognized whitelist). Probes confirm this
372+
/// not on the simulator's recognized catalog). Probes confirm this
373373
/// is the simulator's "honest about what's modeled" stance, distinct
374-
/// from real SQL Server's Msg 448.
374+
/// from real SQL Server's Msg 448. <c>Pashto_CI_AS</c> doesn't ship
375+
/// (only the v100 form does): the prefix is known, the suffix
376+
/// grammar is valid, but the specific combination is absent from
377+
/// the per-prefix tail-set catalog.
375378
/// </summary>
376379
[TestMethod]
377380
public void UnknownCollationName_RaisesNotSupported()
378381
{
379382
var ex = Throws<NotSupportedException>(() => new Simulation().ExecuteNonQuery(
380-
"create table t (s varchar(20) collate Hungarian_CI_AS)"));
381-
Contains("Hungarian_CI_AS", ex.Message);
383+
"create table t (s varchar(20) collate Pashto_CI_AS)"));
384+
Contains("Pashto_CI_AS", ex.Message);
382385
}
383386

384387
/// <summary>

SqlServerSimulator.Tests/CollationMetadataTests.cs

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,18 +114,33 @@ public void Column_WithCollate_RoundTripsViaInformationSchema()
114114
"SELECT COLLATION_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME = 'b'"));
115115
}
116116

117+
/// <summary>
118+
/// Names structurally valid but absent from the catalog raise
119+
/// <c>NotSupportedException</c>. <c>Mapudungan_BIN</c> has a known
120+
/// prefix and a recognized suffix grammar but the specific pair
121+
/// doesn't ship in real SQL Server (probed 2026-05-21: Mapudungan is
122+
/// v100-only).
123+
/// </summary>
117124
[TestMethod]
118125
public void Column_Collate_UnrecognizedName_RaisesNotSupported()
119126
{
120127
var ex = Throws<NotSupportedException>(() => new Simulation().ExecuteNonQuery(
121-
"CREATE TABLE t (a nvarchar(50) COLLATE Japanese_CI_AS)"));
122-
Contains("Japanese_CI_AS", ex.Message);
128+
"CREATE TABLE t (a nvarchar(50) COLLATE Mapudungan_BIN)"));
129+
Contains("Mapudungan_BIN", ex.Message);
123130
Contains("recognized list", ex.Message);
124131
}
125132

133+
/// <summary>
134+
/// The simulator's catalog matches real SQL Server's
135+
/// <c>sys.fn_helpcollations()</c> count — 77 SQL_* names + 5463
136+
/// non-SQL_* names = 5540 total, probed against SQL Server 2025 on
137+
/// 2026-05-21. The parser validates names against the per-prefix
138+
/// tail-set catalog so phantom combinations (grammar-valid but never
139+
/// shipped) are rejected.
140+
/// </summary>
126141
[TestMethod]
127142
public void FnHelpCollations_ListsRecognized()
128-
=> AreEqual(26, new Simulation().ExecuteScalar(
143+
=> AreEqual(5540, new Simulation().ExecuteScalar(
129144
"SELECT COUNT(*) FROM sys.fn_helpcollations()"));
130145

131146
[TestMethod]
@@ -151,6 +166,39 @@ public void FnHelpCollations_ColumnsAreNameAndDescription()
151166
"SELECT name FROM sys.fn_helpcollations WHERE name = 'Latin1_General_100_CI_AS'"));
152167
}
153168

169+
/// <summary>
170+
/// Parser-driven catalog accepts the full breadth of real SQL Server
171+
/// 2025 names — locale × version × flag combinations beyond the 26
172+
/// hand-tuned entries the prior implementation maintained. Probed
173+
/// names from each pattern bucket: a v100-only locale (Pattern_0), a
174+
/// versioned-and-unversioned locale (Pattern_1), a SQL_* CP1250
175+
/// variant, and a v140 + VSS combo (Pattern_6).
176+
/// </summary>
177+
[TestMethod]
178+
[DataRow("Albanian_100_CI_AS", "Albanian-100, case-insensitive, accent-sensitive, kanatype-insensitive, width-insensitive")]
179+
[DataRow("Greek_BIN2", "Greek, binary code point comparison sort")]
180+
[DataRow("SQL_Polish_CP1250_CS_AS", "Polish, case-sensitive, accent-sensitive, kanatype-insensitive, width-insensitive for Unicode Data, SQL Server Sort Order 87 on Code Page 1250 for non-Unicode Data")]
181+
[DataRow("Japanese_XJIS_140_CI_AS_KS_WS_VSS", "Japanese-XJIS-140, case-insensitive, accent-sensitive, kanatype-sensitive, width-sensitive, supplementary characters, variation selector sensitive")]
182+
[DataRow("German_PhoneBook_100_CI_AS_KS_WS_SC_UTF8", "German-PhoneBook-100, case-insensitive, accent-sensitive, kanatype-sensitive, width-sensitive, supplementary characters, UTF8")]
183+
public void FnHelpCollations_Description_MatchesProbedSqlServer(string name, string description)
184+
=> AreEqual(description, new Simulation().ExecuteScalar(
185+
$"SELECT description FROM sys.fn_helpcollations WHERE name = '{name}'"));
186+
187+
/// <summary>
188+
/// Grammar-valid but never-shipped name combinations reject — the
189+
/// parser validates against the per-prefix tail-set catalog, not just
190+
/// the suffix grammar. <c>Pashto_CI_AS</c> (unversioned form of a
191+
/// v100-only locale) and <c>Latin1_General_140_BIN</c> (v140 doesn't
192+
/// have BIN/BIN2) are both phantom; both reject.
193+
/// </summary>
194+
[TestMethod]
195+
[DataRow("Pashto_CI_AS")]
196+
[DataRow("Latin1_General_140_BIN")]
197+
[DataRow("Albanian_BIN2_UTF8")]
198+
public void PhantomCollationName_RejectedByParser(string name)
199+
=> Throws<NotSupportedException>(() => new Simulation().ExecuteNonQuery(
200+
$"CREATE TABLE t (a nvarchar(50) COLLATE {name})"));
201+
154202
[TestMethod]
155203
public void SysDatabases_RowShape_CarriesCompatibilityAndIsolation()
156204
{

SqlServerSimulator/BuiltInResources.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2348,7 +2348,7 @@ private static IEnumerable<SqlValue[]> EnumerateSysServers(Parser.BatchContext b
23482348

23492349
/// <summary>
23502350
/// Rows for <c>sys.fn_helpcollations()</c>. Emits one row per entry in
2351-
/// <see cref="Collation.Recognized"/> — the simulator's whitelist of
2351+
/// <see cref="Collation.IsRecognized"/> — the simulator's whitelist of
23522352
/// metadata-accepted collation names. Real SQL Server returns ~5540
23532353
/// rows here; the simulator's shorter list is honest about which
23542354
/// collation names round-trip through <see cref="Database.CollationName"/>
@@ -2357,8 +2357,8 @@ private static IEnumerable<SqlValue[]> EnumerateSysServers(Parser.BatchContext b
23572357
private static IEnumerable<SqlValue[]> EnumerateFnHelpCollations(Parser.BatchContext batch, Database database)
23582358
{
23592359
_ = batch;
2360-
foreach (var entry in Collation.Recognized.OrderBy(e => e.Key, StringComparer.Ordinal))
2361-
yield return [SqlValue.FromSystemName(entry.Key), SqlValue.FromNVarchar(entry.Value)];
2360+
foreach (var (entryName, entryDesc) in Collation.EnumerateRecognized().OrderBy(e => e.Name, StringComparer.Ordinal))
2361+
yield return [SqlValue.FromSystemName(entryName), SqlValue.FromNVarchar(entryDesc)];
23622362
}
23632363

23642364
private static IEnumerable<SqlValue[]> EnumerateSysSequences(Parser.BatchContext batch, Database database)

0 commit comments

Comments
 (0)