|
| 1 | +using static Microsoft.VisualStudio.TestTools.UnitTesting.Assert; |
| 2 | + |
| 3 | +namespace SqlServerSimulator; |
| 4 | + |
| 5 | +/// <summary> |
| 6 | +/// <c>ASCII</c> / <c>UNICODE</c> / <c>CHAR</c> / <c>NCHAR</c> char-code |
| 7 | +/// scalars. Probe-confirmed verbatim against SQL Server 2025 (2026-05-14); |
| 8 | +/// every test below corresponds to a probe entry. |
| 9 | +/// </summary> |
| 10 | +[TestClass] |
| 11 | +public sealed class CharScalarTests |
| 12 | +{ |
| 13 | + [TestMethod] |
| 14 | + public void Ascii_Letter_ReturnsCode() |
| 15 | + => AreEqual(65, new Simulation().ExecuteScalar("select ASCII('A')")); |
| 16 | + |
| 17 | + [TestMethod] |
| 18 | + public void Ascii_MultiChar_ReturnsFirstByte() |
| 19 | + => AreEqual(65, new Simulation().ExecuteScalar("select ASCII('Abc')")); |
| 20 | + |
| 21 | + [TestMethod] |
| 22 | + public void Ascii_EmptyString_ReturnsNull() |
| 23 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select ASCII('')")); |
| 24 | + |
| 25 | + [TestMethod] |
| 26 | + public void Ascii_Null_ReturnsNull() |
| 27 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select ASCII(NULL)")); |
| 28 | + |
| 29 | + [TestMethod] |
| 30 | + public void Ascii_Space_Returns32() |
| 31 | + => AreEqual(32, new Simulation().ExecuteScalar("select ASCII(' ')")); |
| 32 | + |
| 33 | + /// <summary> |
| 34 | + /// N'€' is U+20AC, representable in CP1252 as 0x80 = 128. |
| 35 | + /// </summary> |
| 36 | + [TestMethod] |
| 37 | + public void Ascii_Cp1252Representable_Returns128() |
| 38 | + => AreEqual(128, new Simulation().ExecuteScalar("select ASCII(N'€')")); |
| 39 | + |
| 40 | + /// <summary> |
| 41 | + /// 'é' is U+00E9 = 233 in CP1252. |
| 42 | + /// </summary> |
| 43 | + [TestMethod] |
| 44 | + public void Ascii_LatinE_Returns233() |
| 45 | + => AreEqual(233, new Simulation().ExecuteScalar("select ASCII('é')")); |
| 46 | + |
| 47 | + /// <summary> |
| 48 | + /// ASCII(65) stringifies 65 to "65" first, then returns ASCII of '6' = 54. |
| 49 | + /// </summary> |
| 50 | + [TestMethod] |
| 51 | + public void Ascii_IntInput_ImplicitStringifies() |
| 52 | + => AreEqual(54, new Simulation().ExecuteScalar("select ASCII(65)")); |
| 53 | + |
| 54 | + [TestMethod] |
| 55 | + public void Unicode_Letter_Returns65() |
| 56 | + => AreEqual(65, new Simulation().ExecuteScalar("select UNICODE(N'A')")); |
| 57 | + |
| 58 | + [TestMethod] |
| 59 | + public void Unicode_EmptyString_ReturnsNull() |
| 60 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select UNICODE(N'')")); |
| 61 | + |
| 62 | + [TestMethod] |
| 63 | + public void Unicode_Null_ReturnsNull() |
| 64 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select UNICODE(NULL)")); |
| 65 | + |
| 66 | + [TestMethod] |
| 67 | + public void Unicode_EuroSign_Returns8364() |
| 68 | + => AreEqual(8364, new Simulation().ExecuteScalar("select UNICODE(N'€')")); |
| 69 | + |
| 70 | + /// <summary> |
| 71 | + /// 😀 (U+1F600) is the high surrogate 0xD83D = 55357 + low surrogate 0xDE00. |
| 72 | + /// Non-SC default collation returns the high surrogate, not the full code point. |
| 73 | + /// </summary> |
| 74 | + [TestMethod] |
| 75 | + public void Unicode_Supplementary_ReturnsHighSurrogate() |
| 76 | + => AreEqual(55357, new Simulation().ExecuteScalar("select UNICODE(N'😀')")); |
| 77 | + |
| 78 | + [TestMethod] |
| 79 | + public void Unicode_IntInput_ImplicitStringifies() |
| 80 | + => AreEqual(54, new Simulation().ExecuteScalar("select UNICODE(65)")); |
| 81 | + |
| 82 | + [TestMethod] |
| 83 | + public void Char_65_ReturnsA() |
| 84 | + => AreEqual("A", new Simulation().ExecuteScalar("select CHAR(65)")); |
| 85 | + |
| 86 | + [TestMethod] |
| 87 | + public void Char_0_ReturnsNulByte() |
| 88 | + { |
| 89 | + // CHAR(0) is a valid character — the NUL byte — not NULL. DATALENGTH |
| 90 | + // confirms a single byte; ASCII round-trips back to 0. |
| 91 | + var sim = new Simulation(); |
| 92 | + AreEqual(1, sim.ExecuteScalar("select datalength(CHAR(0))")); |
| 93 | + AreEqual(0, sim.ExecuteScalar("select ASCII(CHAR(0))")); |
| 94 | + } |
| 95 | + |
| 96 | + [TestMethod] |
| 97 | + public void Char_256_ReturnsNull() |
| 98 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select CHAR(256)")); |
| 99 | + |
| 100 | + [TestMethod] |
| 101 | + public void Char_Negative_ReturnsNull() |
| 102 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select CHAR(-1)")); |
| 103 | + |
| 104 | + [TestMethod] |
| 105 | + public void Char_Null_ReturnsNull() |
| 106 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select CHAR(NULL)")); |
| 107 | + |
| 108 | + [TestMethod] |
| 109 | + public void Char_DecimalInput_TruncatesToInt() |
| 110 | + => AreEqual("A", new Simulation().ExecuteScalar("select CHAR(65.7)")); |
| 111 | + |
| 112 | + [TestMethod] |
| 113 | + public void Char_StringInput_ParsesAsInt() |
| 114 | + => AreEqual("A", new Simulation().ExecuteScalar("select CHAR('65')")); |
| 115 | + |
| 116 | + /// <summary> |
| 117 | + /// CHAR(128) maps to € under CP1252. |
| 118 | + /// </summary> |
| 119 | + [TestMethod] |
| 120 | + public void Char_Cp1252EuroByte_Returns() |
| 121 | + => AreEqual("€", new Simulation().ExecuteScalar("select CHAR(128)")); |
| 122 | + |
| 123 | + [TestMethod] |
| 124 | + public void NChar_65_ReturnsA() |
| 125 | + => AreEqual("A", new Simulation().ExecuteScalar("select NCHAR(65)")); |
| 126 | + |
| 127 | + [TestMethod] |
| 128 | + public void NChar_EuroCodepoint_ReturnsEuro() |
| 129 | + => AreEqual("€", new Simulation().ExecuteScalar("select NCHAR(8364)")); |
| 130 | + |
| 131 | + /// <summary> |
| 132 | + /// Default non-SC collation: NCHAR > 65535 returns NULL rather than |
| 133 | + /// emitting a surrogate pair. Documented in CLAUDE.md as a deliberate |
| 134 | + /// alignment with the simulator's default-collation-only stance. |
| 135 | + /// </summary> |
| 136 | + [TestMethod] |
| 137 | + public void NChar_Supplementary_ReturnsNull() |
| 138 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select NCHAR(65536)")); |
| 139 | + |
| 140 | + [TestMethod] |
| 141 | + public void NChar_Emoji_ReturnsNull() |
| 142 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select NCHAR(128512)")); |
| 143 | + |
| 144 | + [TestMethod] |
| 145 | + public void NChar_Negative_ReturnsNull() |
| 146 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select NCHAR(-1)")); |
| 147 | + |
| 148 | + [TestMethod] |
| 149 | + public void NChar_Null_ReturnsNull() |
| 150 | + => AreEqual(DBNull.Value, new Simulation().ExecuteScalar("select NCHAR(NULL)")); |
| 151 | + |
| 152 | + [TestMethod] |
| 153 | + public void NChar_StringInput_ParsesAsInt() |
| 154 | + => AreEqual("A", new Simulation().ExecuteScalar("select NCHAR('65')")); |
| 155 | + |
| 156 | + [TestMethod] |
| 157 | + public void Char_Width_IsOneByte() |
| 158 | + => AreEqual(1, new Simulation().ExecuteScalar("select datalength(CHAR(65))")); |
| 159 | + |
| 160 | + [TestMethod] |
| 161 | + public void NChar_Width_IsTwoBytes() |
| 162 | + => AreEqual(2, new Simulation().ExecuteScalar("select datalength(NCHAR(65))")); |
| 163 | + |
| 164 | + [TestMethod] |
| 165 | + public void Char13Plus10_EmbedsCRLF() |
| 166 | + { |
| 167 | + // The PRINT-bundle observation that drove this feature: CHAR(13) + |
| 168 | + // CHAR(10) is the idiomatic way to embed CR+LF in T-SQL string |
| 169 | + // literals. |
| 170 | + var sim = new Simulation(); |
| 171 | + AreEqual(2, sim.ExecuteScalar("select len(CHAR(13)+CHAR(10))")); |
| 172 | + AreEqual("a\nb", sim.ExecuteScalar("select 'a'+CHAR(10)+'b'")); |
| 173 | + } |
| 174 | +} |
0 commit comments