@@ -14,6 +14,8 @@ public sealed class CastTests
1414 [ DataRow ( "cast(255 as tinyint)" , ( byte ) 255 ) ]
1515 [ DataRow ( "cast(1 as bit)" , true ) ]
1616 [ DataRow ( "cast(0 as bit)" , false ) ]
17+ [ DataRow ( "cast(-1 as int)" , - 1 ) ] // regression: unary minus inside CAST
18+ [ DataRow ( "cast(-(2 + 3) as int)" , - 5 ) ] // unary minus + parenthesized
1719 public void Cast ( string expression , object expected ) =>
1820 AreEqual ( expected , ExecuteScalar ( $ "select { expression } ") ) ;
1921
@@ -25,15 +27,118 @@ public void Cast_NullPassesThroughWithRetargetedType() =>
2527 public void Cast_NarrowingOverflow_RaisesArithmeticOverflow ( )
2628 {
2729 var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast(300 as tinyint)" ) ) ;
28- StringAssert . Contains ( ex . Message , "Arithmetic overflow" ) ;
30+ Contains ( "Arithmetic overflow" , ex . Message ) ;
2931 }
3032
3133 [ TestMethod ]
32- public void Cast_CrossCategory_NotSupported ( )
34+ [ DataRow ( "cast('42' as int)" , 42 ) ]
35+ [ DataRow ( "cast('-42' as int)" , - 42 ) ]
36+ [ DataRow ( "cast('+42' as int)" , 42 ) ]
37+ [ DataRow ( "cast(' 42 ' as int)" , 42 ) ] // whitespace tolerated
38+ [ DataRow ( "cast('007' as int)" , 7 ) ] // leading zeros fine
39+ [ DataRow ( "cast('' as int)" , 0 ) ] // SQL Server quirk: empty → 0
40+ [ DataRow ( "cast(' ' as int)" , 0 ) ] // whitespace-only → 0
41+ public void Cast_StringToInt32 ( string expression , int expected ) =>
42+ AreEqual ( expected , ExecuteScalar ( $ "select { expression } ") ) ;
43+
44+ [ TestMethod ]
45+ [ DataRow ( "'abc'" , "Conversion failed when converting the varchar value 'abc' to data type int." ) ]
46+ [ DataRow ( "'42.5'" , "Conversion failed when converting the varchar value '42.5' to data type int." ) ]
47+ [ DataRow ( "'0x42'" , "Conversion failed when converting the varchar value '0x42' to data type int." ) ]
48+ public void Cast_BadStringToInt32_RaisesMsg245 ( string source , string expected )
49+ {
50+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( $ "select cast({ source } as int)") ) ;
51+ AreEqual ( expected , ex . Message ) ;
52+ }
53+
54+ [ TestMethod ]
55+ public void Cast_NVarcharToInt32_UsesNVarcharInErrorMessage ( )
56+ {
57+ // Source-type wording in Msg 245 reflects the actual source type.
58+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast(N'abc' as int)" ) ) ;
59+ AreEqual ( "Conversion failed when converting the nvarchar value 'abc' to data type int." , ex . Message ) ;
60+ }
61+
62+ [ TestMethod ]
63+ public void Cast_StringOverflow_TinyInt_RaisesMsg244WithINT1 ( )
64+ {
65+ // Tinyint/smallint use Msg 244 with internal SQL names INT1/INT2.
66+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('300' as tinyint)" ) ) ;
67+ AreEqual ( "The conversion of the varchar value '300' overflowed an INT1 column. Use a larger integer column." , ex . Message ) ;
68+ }
69+
70+ [ TestMethod ]
71+ public void Cast_StringOverflow_SmallInt_RaisesMsg244WithINT2 ( )
72+ {
73+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('99999' as smallint)" ) ) ;
74+ AreEqual ( "The conversion of the varchar value '99999' overflowed an INT2 column. Use a larger integer column." , ex . Message ) ;
75+ }
76+
77+ [ TestMethod ]
78+ public void Cast_StringOverflow_Int_RaisesMsg248 ( )
79+ {
80+ // Int has its own overflow message (Msg 248) — distinct text and no
81+ // "Use a larger integer column" suffix.
82+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('99999999999' as int)" ) ) ;
83+ AreEqual ( "The conversion of the varchar value '99999999999' overflowed an int column." , ex . Message ) ;
84+ }
85+
86+ [ TestMethod ]
87+ public void Cast_StringOverflow_BigInt_RaisesMsg8115 ( )
88+ {
89+ // Bigint overflow falls through to the generic arithmetic-overflow
90+ // message; no source-value detail.
91+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('99999999999999999999' as bigint)" ) ) ;
92+ Contains ( "Arithmetic overflow" , ex . Message ) ;
93+ Contains ( "bigint" , ex . Message ) ;
94+ }
95+
96+ [ TestMethod ]
97+ [ DataRow ( "'1'" , true ) ]
98+ [ DataRow ( "'0'" , false ) ]
99+ [ DataRow ( "'true'" , true ) ]
100+ [ DataRow ( "'TRUE'" , true ) ]
101+ [ DataRow ( "'false'" , false ) ]
102+ [ DataRow ( "'FALSE'" , false ) ]
103+ [ DataRow ( "' true '" , true ) ] // surrounding whitespace tolerated
104+ [ DataRow ( "'2'" , true ) ] // any non-zero numeric → true
105+ [ DataRow ( "'-1'" , true ) ]
106+ [ DataRow ( "'000'" , false ) ] // all-zero digit string → false
107+ [ DataRow ( "''" , false ) ] // empty → false
108+ [ DataRow ( "' '" , false ) ] // whitespace-only → false
109+ [ DataRow ( "'99999999999999999999'" , true ) ] // exceeds long but bit ignores magnitude
110+ public void Cast_StringToBit ( string source , bool expected ) =>
111+ AreEqual ( expected , ExecuteScalar ( $ "select cast({ source } as bit)") ) ;
112+
113+ [ TestMethod ]
114+ [ DataRow ( "'yes'" ) ]
115+ [ DataRow ( "'no'" ) ]
116+ [ DataRow ( "'t'" ) ]
117+ [ DataRow ( "'truex'" ) ]
118+ [ DataRow ( "'1.0'" ) ] // decimal point not accepted even for bit
119+ public void Cast_BadStringToBit_RaisesMsg245 ( string source )
120+ {
121+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( $ "select cast({ source } as bit)") ) ;
122+ Contains ( "Conversion failed" , ex . Message ) ;
123+ Contains ( "to data type bit" , ex . Message ) ;
124+ }
125+
126+ [ TestMethod ]
127+ [ DataRow ( "cast(42 as varchar(10))" , "42" ) ]
128+ [ DataRow ( "cast(-42 as varchar(10))" , "-42" ) ]
129+ [ DataRow ( "cast(0 as varchar(10))" , "0" ) ]
130+ [ DataRow ( "cast(cast(255 as tinyint) as varchar(5))" , "255" ) ]
131+ [ DataRow ( "cast(cast(1 as bit) as varchar(5))" , "1" ) ]
132+ [ DataRow ( "cast(cast(0 as bit) as varchar(5))" , "0" ) ]
133+ public void Cast_IntegerToString ( string expression , string expected ) =>
134+ AreEqual ( expected , ExecuteScalar ( $ "select { expression } ") ) ;
135+
136+ [ TestMethod ]
137+ public void Cast_BigIntToVarchar_PreservesFullRange ( )
33138 {
34- // String ↔ numeric coercion isn't modeled yet; CAST surfaces the same
35- // NotSupportedException SqlValue.CoerceTo throws .
36- _ = Throws < NotSupportedException > ( ( ) => ExecuteScalar ( "select cast('42 ' as int )" ) ) ;
139+ // Numeric literal tokens are int-bounded; the source bigint here is
140+ // constructed by parsing a string instead .
141+ AreEqual ( "9223372036854775807" , ExecuteScalar ( "select cast(cast('9223372036854775807 ' as bigint) as varchar(30) )" ) ) ;
37142 }
38143
39144 [ TestMethod ]
@@ -261,4 +366,93 @@ public void Cast_DateTimeOffsetToTime_DropsDateAndOffset()
261366 [ TestMethod ]
262367 public void Cast_DateTimeOffsetNullPassesThrough ( ) =>
263368 IsInstanceOfType < DBNull > ( ExecuteScalar ( "select cast(cast(null as datetimeoffset(7)) as varchar(40))" ) ) ;
369+
370+ [ TestMethod ]
371+ public void Cast_UnknownTypeName_RaisesMsg243 ( )
372+ {
373+ // CAST takes a different error path than CREATE TABLE: Msg 243
374+ // ("Type X is not a defined system type.") instead of Msg 2715.
375+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('x' as intz)" ) ) ;
376+ AreEqual ( "Type intz is not a defined system type." , ex . Message ) ;
377+ }
378+
379+ [ TestMethod ]
380+ public void Cast_LengthSpecifierOnFixedType_RaisesMsg291 ( )
381+ {
382+ // CAST equivalent of CREATE TABLE's Msg 2716. Note the absence of a
383+ // trailing period — SQL Server's Msg 291 message lacks one.
384+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('x' as int(4))" ) ) ;
385+ AreEqual ( "CAST or CONVERT: invalid attributes specified for type 'int'" , ex . Message ) ;
386+ }
387+
388+ [ TestMethod ]
389+ public void Cast_VarcharSizeExceedsMaximum_UsesTypeWording ( )
390+ {
391+ // CAST form of Msg 131 says "given to the type 'T'" rather than the
392+ // column-context "given to the column 'V'".
393+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('x' as varchar(8001))" ) ) ;
394+ AreEqual ( "The size (8001) given to the type 'varchar' exceeds the maximum allowed for any data type (8000)." , ex . Message ) ;
395+ }
396+
397+ [ TestMethod ]
398+ public void Cast_VarbinarySizeExceedsMaximum_UsesTypeWording ( )
399+ {
400+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast(0xAB as varbinary(8001))" ) ) ;
401+ AreEqual ( "The size (8001) given to the type 'varbinary' exceeds the maximum allowed for any data type (8000)." , ex . Message ) ;
402+ }
403+
404+ [ TestMethod ]
405+ public void Cast_NVarcharSizeExceedsMaximum_UsesConvertSpecificationWording ( )
406+ {
407+ // nvarchar in CAST has its own phrasing distinct from varchar.
408+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast(N'x' as nvarchar(4001))" ) ) ;
409+ AreEqual ( "The size (4001) given to the convert specification 'nvarchar' exceeds the maximum allowed for any data type (4000)." , ex . Message ) ;
410+ }
411+
412+ [ TestMethod ]
413+ public void Cast_InvalidScale_OnLine1_PrefixesLine1 ( )
414+ {
415+ // Msg 1002 is one of the few SQL Server errors that puts the line
416+ // number into the message text itself.
417+ var ex = Throws < DbException > ( ( ) => ExecuteScalar ( "select cast('x' as datetime2(8))" ) ) ;
418+ AreEqual ( "Line 1: Specified scale 8 is invalid." , ex . Message ) ;
419+ }
420+
421+ [ TestMethod ]
422+ public void Cast_InvalidScale_OnLaterLine_PrefixesActualLine ( )
423+ {
424+ // The "Line N" tracks the line of the offending type token, not a
425+ // fixed line 1 — verified against real SQL Server. Single statement
426+ // with leading blank lines so ExecuteScalar surfaces the parse error
427+ // immediately (multi-statement batches parse lazily; see the
428+ // semicolon-batch variant below).
429+ var ex = Throws < DbException > ( ( ) => ExecuteScalar (
430+ "\n " +
431+ "\n " +
432+ "select cast('x' as datetime2(8))" ) ) ;
433+ AreEqual ( "Line 3: Specified scale 8 is invalid." , ex . Message ) ;
434+ }
435+
436+ [ TestMethod ]
437+ public void Cast_InvalidScale_InLaterStatementOfBatch_FiresWhenReaderReachesIt ( )
438+ {
439+ // Multi-statement batches are parsed lazily one statement at a time
440+ // (CreateResultSetsForCommand is an iterator). The third statement's
441+ // parse error doesn't fire until the consumer asks for its result
442+ // set via NextResult.
443+ using var connection = new Simulation ( ) . CreateOpenConnection ( ) ;
444+ using var command = connection . CreateCommand (
445+ "select 1;\n " +
446+ "select 2;\n " +
447+ "select cast('x' as datetime2(8))" ) ;
448+ using var reader = command . ExecuteReader ( ) ;
449+
450+ IsTrue ( reader . NextResult ( ) ) ; // advance past statement 1
451+ var ex = Throws < DbException > ( ( ) =>
452+ {
453+ _ = reader . NextResult ( ) ; // forces parse of statement 3
454+ _ = reader . NextResult ( ) ;
455+ } ) ;
456+ AreEqual ( "Line 3: Specified scale 8 is invalid." , ex . Message ) ;
457+ }
264458}
0 commit comments