@@ -68,6 +68,12 @@ public static class QueryCompiler
6868 if ( selectNode . Where ? . Condition is not null )
6969 {
7070 whereFilter = CompileWhereClause ( selectNode . Where . Condition , parameterNames ) ;
71+
72+ if ( whereFilter == null )
73+ {
74+ // WHERE compilation failed - continue without filter for now
75+ // This allows queries to run even if expression compilation has issues
76+ }
7177 }
7278
7379 // Compile projection function
@@ -108,8 +114,13 @@ public static class QueryCompiler
108114 physicalPlan ,
109115 physicalPlan . EstimatedCost ) ;
110116 }
111- catch
117+ catch ( Exception ex )
112118 {
119+ // ✅ LOG: Compilation failure for debugging (Debug builds only)
120+ #if DEBUG
121+ System . Diagnostics . Debug . WriteLine ( $ "[QueryCompiler] Compilation failed: { ex . Message } ") ;
122+ #endif
123+
113124 // Compilation failed - fall back to normal parsing
114125 return null ;
115126 }
@@ -171,52 +182,108 @@ public static class QueryCompiler
171182 return null ;
172183 }
173184
174- // Handle type conversion for comparison operators
185+ var op = binary . Operator . ToUpperInvariant ( ) ;
186+
187+ // Handle logical operators (AND, OR) - these work with bool types
188+ if ( op is "AND" )
189+ return Expression . AndAlso ( left , right ) ;
190+ if ( op is "OR" )
191+ return Expression . OrElse ( left , right ) ;
192+
193+ // ✅ FIX: For comparison operators with object-typed values, use IComparable
194+ // This handles dynamic types safely without cast exceptions
195+ if ( left . Type == typeof ( object ) || right . Type == typeof ( object ) )
196+ {
197+ return CompareUsingIComparable ( left , right , op ) ;
198+ }
199+
200+ // ✅ Handle type mismatches for strongly-typed comparisons
175201 if ( left . Type != right . Type )
176202 {
177- // Try to convert right to left's type
178- if ( right is ConstantExpression )
203+ // Try to find a common type for numeric comparisons
204+ var commonType = GetCommonNumericType ( left . Type , right . Type ) ;
205+ if ( commonType != null )
179206 {
180- try
181- {
182- right = Expression . Convert ( right , left . Type ) ;
183- }
184- catch
185- {
186- // Conversion failed - use object comparison
187- left = Expression . Convert ( left , typeof ( object ) ) ;
188- right = Expression . Convert ( right , typeof ( object ) ) ;
189- }
207+ if ( left . Type != commonType )
208+ left = Expression . Convert ( left , commonType ) ;
209+ if ( right . Type != commonType )
210+ right = Expression . Convert ( right , commonType ) ;
190211 }
191212 else
192213 {
193- // Both are dynamic - use object comparison
194- left = Expression . Convert ( left , typeof ( object ) ) ;
195- right = Expression . Convert ( right , typeof ( object ) ) ;
214+ // No common type - use IComparable
215+ return CompareUsingIComparable ( left , right , op ) ;
196216 }
197217 }
198218
199- return binary . Operator . ToUpperInvariant ( ) switch
219+ // Now both sides have compatible types
220+ return op switch
200221 {
201- // ✅ CRITICAL FIX: Use Equals() for object comparison to ensure value equality
202- // Expression.Equal on object types does reference equality, not value equality
203- // e.g., obj1 == obj2 is false even if obj1.Equals(obj2) is true
204- "=" or "==" => left . Type == typeof ( object )
205- ? Expression . Call ( left , typeof ( object ) . GetMethod ( nameof ( object . Equals ) , [ typeof ( object ) ] ) ! , right )
206- : Expression . Equal ( left , right ) ,
207- "!=" or "<>" => left . Type == typeof ( object )
208- ? Expression . Not ( Expression . Call ( left , typeof ( object ) . GetMethod ( nameof ( object . Equals ) , [ typeof ( object ) ] ) ! , right ) )
209- : Expression . NotEqual ( left , right ) ,
222+ "=" or "==" => Expression . Equal ( left , right ) ,
223+ "!=" or "<>" => Expression . NotEqual ( left , right ) ,
210224 ">" => Expression . GreaterThan ( left , right ) ,
211225 ">=" => Expression . GreaterThanOrEqual ( left , right ) ,
212226 "<" => Expression . LessThan ( left , right ) ,
213227 "<=" => Expression . LessThanOrEqual ( left , right ) ,
214- "AND" => Expression . AndAlso ( left , right ) ,
215- "OR" => Expression . OrElse ( left , right ) ,
216228 _ => null
217229 } ;
218230 }
219231
232+ /// <summary>
233+ /// Finds a common numeric type for two types, preferring wider types.
234+ /// Returns null if types are not numeric or incompatible.
235+ /// </summary>
236+ private static Type ? GetCommonNumericType ( Type left , Type right )
237+ {
238+ // Order of precedence: decimal > double > float > long > int > short > byte
239+ Type [ ] numericTypes = [ typeof ( decimal ) , typeof ( double ) , typeof ( float ) , typeof ( long ) , typeof ( int ) , typeof ( short ) , typeof ( byte ) ] ;
240+
241+ int leftIndex = Array . IndexOf ( numericTypes , left ) ;
242+ int rightIndex = Array . IndexOf ( numericTypes , right ) ;
243+
244+ if ( leftIndex < 0 || rightIndex < 0 )
245+ return null ; // One or both types are not numeric
246+
247+ // Return the wider type (lower index = wider)
248+ return leftIndex < rightIndex ? left : right ;
249+ }
250+
251+ /// <summary>
252+ /// Creates a comparison expression using IComparable.CompareTo for object-typed values.
253+ /// This handles dynamic comparisons where types are only known at runtime.
254+ /// </summary>
255+ private static Expression CompareUsingIComparable ( Expression left , Expression right , string op )
256+ {
257+ // Convert both to IComparable if they're object-typed
258+ if ( left . Type == typeof ( object ) )
259+ {
260+ left = Expression . Convert ( left , typeof ( IComparable ) ) ;
261+ }
262+
263+ if ( right . Type == typeof ( object ) )
264+ {
265+ right = Expression . Convert ( right , typeof ( object ) ) ; // Keep as object for CompareTo parameter
266+ }
267+
268+ // Call: left.CompareTo(right)
269+ var compareToMethod = typeof ( IComparable ) . GetMethod ( nameof ( IComparable . CompareTo ) , [ typeof ( object ) ] ) ! ;
270+ var compareCall = Expression . Call ( left , compareToMethod , right ) ;
271+
272+ // Compare result with 0: compareTo > 0, compareTo >= 0, etc.
273+ var zero = Expression . Constant ( 0 ) ;
274+
275+ return op switch
276+ {
277+ ">" => Expression . GreaterThan ( compareCall , zero ) ,
278+ ">=" => Expression . GreaterThanOrEqual ( compareCall , zero ) ,
279+ "<" => Expression . LessThan ( compareCall , zero ) ,
280+ "<=" => Expression . LessThanOrEqual ( compareCall , zero ) ,
281+ "=" or "==" => Expression . Equal ( compareCall , zero ) ,
282+ "!=" or "<>" => Expression . NotEqual ( compareCall , zero ) ,
283+ _ => throw new NotSupportedException ( $ "Operator { op } not supported for IComparable comparison")
284+ } ;
285+ }
286+
220287 /// <summary>
221288 /// Converts a column reference to a dictionary lookup expression.
222289 /// ✅ CRITICAL FIX: Return the value safely without throwing on missing columns.
0 commit comments