@@ -7,15 +7,33 @@ namespace SqlServerSimulator;
77/// <summary>
88/// Bridges .NET's native types, the various <see cref="DbType"/>s, and SQL Server's actual behavior.
99/// </summary>
10- internal abstract class DataType
10+ internal abstract class DataType : IComparer < DataValue > , IComparable < DataType >
1111{
1212 private protected DataType ( )
1313 {
1414 }
1515
1616 public abstract DbType Type { get ; }
1717
18- public abstract object ConvertFrom ( object value ) ;
18+ /// <summary>
19+ /// Precedence according to the ranking at https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-type-precedence-transact-sql
20+ /// </summary>
21+ protected abstract int Precedence { get ; }
22+
23+ public int CompareTo ( DataType ? other )
24+ {
25+ ArgumentNullException . ThrowIfNull ( other ) ;
26+
27+ return this == other ? 0 : this . CompareTo ( other ) ;
28+ }
29+
30+ public DataValue ConvertFrom ( object ? value ) => ConvertFrom ( new DataValue ( value , this ) ) ;
31+
32+ public abstract DataValue ConvertFrom ( DataValue value ) ;
33+
34+ public abstract int Compare ( DataValue x , DataValue y ) ;
35+
36+ public abstract int DataLength ( DataValue value ) ;
1937
2038 public override string ToString ( ) => Type . ToString ( ) ;
2139
@@ -33,6 +51,32 @@ private protected DataType()
3351
3452 public static readonly DataType BuiltInDbSystemName = new DbSystemName ( ) ;
3553
54+ public static NumericCompatibleDataType CommonNumeric ( DataValue a , DataValue b , char op )
55+ {
56+ var ( at , bt ) = ( a . Type , b . Type ) ;
57+ return at is not NumericCompatibleDataType atb || bt is not NumericCompatibleDataType btb
58+ ? throw SimulatedSqlException . DataTypeIncompatible ( at , bt , op )
59+ : at . CompareTo ( bt ) switch
60+ {
61+ < 0 => atb ,
62+ 0 => atb ,
63+ > 0 => btb ,
64+ } ;
65+ }
66+
67+ public static BitwiseCompatibleDataType CommonInteger ( DataValue a , DataValue b , char op )
68+ {
69+ var ( at , bt ) = ( a . Type , b . Type ) ;
70+ return at is not BitwiseCompatibleDataType atb || bt is not BitwiseCompatibleDataType btb
71+ ? throw SimulatedSqlException . DataTypeIncompatible ( at , bt , op )
72+ : at . CompareTo ( bt ) switch
73+ {
74+ < 0 => atb ,
75+ 0 => atb ,
76+ > 0 => btb ,
77+ } ;
78+ }
79+
3680 /// <summary>
3781 /// Looks up the <see cref="DataType"/> for the provided type name.
3882 /// </summary>
@@ -76,46 +120,200 @@ public static DataType GetByName(Name name, int index)
76120 _ => throw new NotSupportedException ( $ "Simulated data type parser doesn't recognize DbType { dbType } ") ,
77121 } ;
78122
79- private sealed class DbBoolean : DataType
123+ public abstract class NumericCompatibleDataType : DataType
124+ {
125+ protected abstract int DataLength ( ) ;
126+
127+ public sealed override int DataLength ( DataValue value ) => DataLength ( ) ;
128+
129+ public abstract object ? Add ( object ? a , object ? b ) ;
130+
131+ public DataValue Add ( DataValue a , DataValue b ) => new ( Add ( a . Value , b . Value ) , this ) ;
132+
133+ public abstract object ? Subtract ( object ? a , object ? b ) ;
134+
135+ public DataValue Subtract ( DataValue a , DataValue b ) => new ( Subtract ( a . Value , b . Value ) , this ) ;
136+
137+ public abstract object ? Multiply ( object ? a , object ? b ) ;
138+
139+ public DataValue Multiply ( DataValue a , DataValue b ) => new ( Multiply ( a . Value , b . Value ) , this ) ;
140+
141+ public abstract object ? Divide ( object ? a , object ? b ) ;
142+
143+ public DataValue Divide ( DataValue a , DataValue b ) => new ( Divide ( a . Value , b . Value ) , this ) ;
144+ }
145+
146+ public abstract class BitwiseCompatibleDataType : NumericCompatibleDataType
147+ {
148+ public abstract object ? BitwiseAnd ( object ? a , object ? b ) ;
149+
150+ public DataValue BitwiseAnd ( DataValue a , DataValue b ) => new ( BitwiseAnd ( a . Value , b . Value ) , this ) ;
151+
152+ public abstract object ? BitwiseOr ( object ? a , object ? b ) ;
153+
154+ public DataValue BitwiseOr ( DataValue a , DataValue b ) => new ( BitwiseOr ( a . Value , b . Value ) , this ) ;
155+
156+ public abstract object ? BitwiseExclusiveOr ( object ? a , object ? b ) ;
157+
158+ public DataValue BitwiseExclusiveOr ( DataValue a , DataValue b ) => new ( BitwiseExclusiveOr ( a . Value , b . Value ) , this ) ;
159+ }
160+
161+ private sealed class DbBoolean : BitwiseCompatibleDataType
80162 {
81163 public override DbType Type => DbType . Boolean ;
82164
83- public override object ConvertFrom ( object value ) => Convert . ToBoolean ( value , CultureInfo . InvariantCulture ) ;
165+ protected override int Precedence => 20 ;
166+
167+ public override DataValue ConvertFrom ( DataValue value ) => new ( Convert . ToBoolean ( value , CultureInfo . InvariantCulture ) , this ) ;
168+
169+ public override int Compare ( DataValue x , DataValue y ) => throw new NotImplementedException ( ) ;
170+
171+ protected override int DataLength ( ) => 1 ;
172+
173+ public override object ? Add ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
174+
175+ public override object ? Subtract ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
176+
177+ public override object ? Multiply ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
178+
179+ public override object ? Divide ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
180+
181+ public override object ? BitwiseAnd ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
182+
183+ public override object ? BitwiseOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
184+
185+ public override object ? BitwiseExclusiveOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
84186 }
85187
86- private sealed class DbByte : DataType
188+ private sealed class DbByte : BitwiseCompatibleDataType
87189 {
88190 public override DbType Type => DbType . Byte ;
89191
90- public override object ConvertFrom ( object value ) => Convert . ToByte ( value , CultureInfo . InvariantCulture ) ;
192+ protected override int Precedence => 19 ;
193+
194+ public override DataValue ConvertFrom ( DataValue value ) => new ( Convert . ToByte ( value , CultureInfo . InvariantCulture ) , this ) ;
195+
196+ public override int Compare ( DataValue x , DataValue y ) => throw new NotImplementedException ( ) ;
197+
198+ protected override int DataLength ( ) => 1 ;
199+
200+ public override object ? Add ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
201+
202+ public override object ? Subtract ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
203+
204+ public override object ? Multiply ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
205+
206+ public override object ? Divide ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
207+
208+ public override object ? BitwiseAnd ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
209+
210+ public override object ? BitwiseOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
211+
212+ public override object ? BitwiseExclusiveOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
91213 }
92214
93- private sealed class DbInt16 : DataType
215+ private sealed class DbInt16 : BitwiseCompatibleDataType
94216 {
95217 public override DbType Type => DbType . Int16 ;
96218
97- public override object ConvertFrom ( object value ) => Convert . ToInt16 ( value , CultureInfo . InvariantCulture ) ;
219+ protected override int Precedence => 18 ;
220+
221+ public override DataValue ConvertFrom ( DataValue value ) => new ( Convert . ToInt16 ( value , CultureInfo . InvariantCulture ) , this ) ;
222+
223+ public override int Compare ( DataValue x , DataValue y ) => throw new NotImplementedException ( ) ;
224+
225+ protected override int DataLength ( ) => 2 ;
226+
227+ public override object ? Add ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
228+
229+ public override object ? Subtract ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
230+
231+ public override object ? Multiply ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
232+
233+ public override object ? Divide ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
234+
235+ public override object ? BitwiseAnd ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
236+
237+ public override object ? BitwiseOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
238+
239+ public override object ? BitwiseExclusiveOr ( object ? a , object ? b ) => throw new NotImplementedException ( ) ;
98240 }
99241
100- private sealed class DbInt32 : DataType
242+ private sealed class DbInt32 : BitwiseCompatibleDataType
101243 {
102244 public override DbType Type => DbType . Int32 ;
103245
104- public override object ConvertFrom ( object value ) => Convert . ToInt32 ( value , CultureInfo . InvariantCulture ) ;
246+ protected override int Precedence => 17 ;
247+
248+ public static int ToNative ( object value ) => Convert . ToInt32 ( value , CultureInfo . InvariantCulture ) ;
249+
250+ public override DataValue ConvertFrom ( DataValue value ) => new ( value . Value is null ? null : ToNative ( value . Value ) , this ) ;
251+
252+ public override int Compare ( DataValue x , DataValue y )
253+ {
254+ var ( xv , yv ) = ( x . Value , y . Value ) ;
255+ if ( xv is null )
256+ return yv is null ? 0 : - 1 ;
257+ else if ( yv is null )
258+ return 1 ;
259+
260+ return ToNative ( x ) . CompareTo ( ToNative ( y ) ) ;
261+ }
262+
263+ protected override int DataLength ( ) => 4 ;
264+
265+ private static bool TryToNative ( object ? rawA , object ? rawB , out int a , out int b )
266+ {
267+ if ( rawA is null || rawB is null )
268+ {
269+ a = default ;
270+ b = default ;
271+ return false ;
272+ }
273+
274+ a = ToNative ( rawA ) ;
275+ b = ToNative ( rawB ) ;
276+ return true ;
277+ }
278+
279+ public override object ? Add ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA + nativeB : null ;
280+
281+ public override object ? Subtract ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA - nativeB : null ;
282+
283+ public override object ? Multiply ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA * nativeB : null ;
284+
285+ public override object ? Divide ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA / nativeB : null ;
286+
287+ public override object ? BitwiseAnd ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA & nativeB : null ;
288+
289+ public override object ? BitwiseOr ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA | nativeB : null ;
290+
291+ public override object ? BitwiseExclusiveOr ( object ? a , object ? b ) => TryToNative ( a , b , out var nativeA , out var nativeB ) ? nativeA ^ nativeB : null ;
105292 }
106293
107294 private sealed class DbAnsiString : DataType
108295 {
109296 public override DbType Type => DbType . AnsiString ;
110297
111- public override object ConvertFrom ( object value ) => value . ToString ( ) ?? throw new InvalidOperationException ( "value's ToString method returned null." ) ;
298+ protected override int Precedence => 28 ;
299+
300+ public override DataValue ConvertFrom ( DataValue value ) => new ( value . Value ? . ToString ( ) , this ) ;
301+
302+ public override int Compare ( DataValue x , DataValue y ) => throw new NotImplementedException ( ) ;
303+
304+ public override int DataLength ( DataValue value ) => throw new NotImplementedException ( ) ;
112305 }
113306
114307 private class DbString : DataType
115308 {
116309 public override DbType Type => DbType . String ;
117310
118- public override object ConvertFrom ( object value ) => value . ToString ( ) ?? throw new InvalidOperationException ( "value's ToString method returned null." ) ;
311+ protected override int Precedence => 26 ;
312+
313+ public override DataValue ConvertFrom ( DataValue value ) => new ( value . Value ? . ToString ( ) , this ) ;
314+
315+ public override int Compare ( DataValue x , DataValue y ) => throw new NotImplementedException ( ) ;
316+ public override int DataLength ( DataValue value ) => throw new NotImplementedException ( ) ;
119317 }
120318
121319 private sealed class DbSystemName : DbString
0 commit comments