1- import { LOGICAL_OPERATORS } from "../constants.js" ;
1+ import { LITERAL_TYPES , LOGICAL_OPERATORS } from "../constants.js" ;
22
33/**
44 * Main conversion function that sets up the global context
@@ -48,7 +48,7 @@ function DevExpressConverter() {
4848 return handleFunction ( ast ) ;
4949 case "field" :
5050 case "value" :
51- return convertValue ( ast . value ) ;
51+ return convertValue ( ast . value , parentOperator ) ;
5252 default :
5353 return null ;
5454 }
@@ -113,6 +113,7 @@ function DevExpressConverter() {
113113 if ( shouldFlattenLogicalTree ( parentOperator , operator , ast ) ) {
114114 return flattenLogicalTree ( left , operator , right ) ;
115115 }
116+
116117 return [ left , operator , right ] ;
117118 }
118119
@@ -123,10 +124,11 @@ function DevExpressConverter() {
123124 */
124125 function handleComparisonOperator ( ast ) {
125126 const operator = ast . operator . toUpperCase ( ) ;
127+ const originalOperator = ast . originalOperator ?. toUpperCase ( ) ;
126128
127129 // Handle "IS NULL" condition
128- if ( operator === "IS" && ast . value === null ) {
129- return [ ast . field , "=" , null ] ;
130+ if ( ( operator === "IS" || originalOperator === "IS" ) && ast . value === null ) {
131+ return [ ast . field , "=" , null , { type : originalOperator } , null ] ;
130132 }
131133
132134 // Handle "IN" condition, including comma-separated values
@@ -139,14 +141,21 @@ function DevExpressConverter() {
139141 const right = ast . right !== undefined ? processAstNode ( ast . right ) : convertValue ( ast . value ) ;
140142 const rightDefault = ast . right ?. args [ 1 ] ?. value ;
141143 let operatorToken = ast . operator . toLowerCase ( ) ;
144+ let includeExtradata = false ;
142145
143146 if ( operatorToken === "like" ) {
144147 operatorToken = "contains" ;
145148 } else if ( operatorToken === "not like" ) {
146149 operatorToken = "notcontains" ;
150+ } else if ( operatorToken === "=" && originalOperator === "IS" ) {
151+ includeExtradata = true
152+ } else if ( operatorToken == "!=" && originalOperator === "IS NOT" ) {
153+ operatorToken = "!=" ;
154+ includeExtradata = true ;
147155 }
148-
149156 let comparison = [ left , operatorToken , right ] ;
157+ if ( includeExtradata )
158+ comparison = [ left , operatorToken , right , { type : originalOperator } , right ] ;
150159
151160 // Last null because of special case when using dropdown it https://github.com/DevExpress/DevExtreme/blob/25_1/packages/devextreme/js/__internal/data/m_utils.ts#L18 it takes last value as null
152161 if ( ( ast . left && isFunctionNullCheck ( ast . left , true ) ) || ( ast . value && isFunctionNullCheck ( ast . value , false ) ) ) {
@@ -205,22 +214,51 @@ function DevExpressConverter() {
205214 resolvedValue = resolvedValue . split ( ',' ) . map ( v => v . trim ( ) ) ;
206215 }
207216
217+ // handle short circuit evaluation for IN operator
218+ if ( EnableShortCircuit && ( LITERAL_TYPES . includes ( ast . field ?. type ) && LITERAL_TYPES . includes ( ast . value ?. type ) ) ) {
219+ const fieldVal = convertValue ( ast . field ) ;
220+ if ( Array . isArray ( resolvedValue ) ) {
221+ // normalize numeric strings if LHS is number
222+ const list = resolvedValue . map ( x =>
223+ ( typeof x === "string" && ! isNaN ( x ) && typeof fieldVal === "number" )
224+ ? Number ( x )
225+ : x
226+ ) ;
227+
228+ if ( operator === "IN" )
229+ return list . includes ( fieldVal ) ;
230+ else if ( operator === "NOT IN" )
231+ return ! list . includes ( fieldVal ) ;
232+ } else if ( ! Array . isArray ( resolvedValue ) ) {
233+ // normalize numeric strings if LHS is number
234+ const value = ( typeof resolvedValue === "string" && ! isNaN ( resolvedValue ) && typeof fieldVal === "number" )
235+ ? Number ( resolvedValue )
236+ : resolvedValue ;
237+
238+ if ( operator === "IN" )
239+ return fieldVal == value ;
240+ else if ( operator === "NOT IN" )
241+ return fieldVal != value ;
242+ }
243+ }
244+
208245 let operatorToken = operator === "IN" ? '=' : operator === "NOT IN" ? '!=' : operator ;
209246 let joinOperatorToken = operator === "IN" ? 'or' : operator === "NOT IN" ? 'and' : operator ;
210-
247+ let field = convertValue ( ast . field ) ;
211248 if ( Array . isArray ( resolvedValue ) && resolvedValue . length ) {
212- return resolvedValue . flatMap ( i => [ [ ast . field , operatorToken , i ] , joinOperatorToken ] ) . slice ( 0 , - 1 ) ;
249+ return resolvedValue . flatMap ( i => [ [ field , operatorToken , i ] , joinOperatorToken ] ) . slice ( 0 , - 1 ) ;
213250 }
214251
215- return [ ast . field , operatorToken , resolvedValue ] ;
252+ return [ field , operatorToken , resolvedValue ] ;
216253 }
217254
218255 /**
219256 * Converts a single value, resolving placeholders and handling special cases.
220257 * @param {* } val - The value to convert.
258+ * @param {string } parentOperator - The operator of the parent logical node (if any).
221259 * @returns {* } Converted value.
222260 */
223- function convertValue ( val ) {
261+ function convertValue ( val , parentOperator = null ) {
224262 if ( val === null ) return null ;
225263
226264 // Handle array values
@@ -251,6 +289,10 @@ function DevExpressConverter() {
251289 }
252290 }
253291
292+ if ( parentOperator && parentOperator . toUpperCase ( ) === "IN" && typeof val === "string" ) {
293+ return val . split ( ',' ) . map ( v => v . trim ( ) ) ;
294+ }
295+
254296 return val ;
255297 }
256298
@@ -368,6 +410,10 @@ function DevExpressConverter() {
368410
369411 if ( ( left !== null && isNaN ( left ) ) || ( right !== null && isNaN ( right ) ) ) return null ;
370412
413+ // Handle NULL == 0 OR NULL == "" cases
414+ if ( left === null && ( right == 0 || right == "" ) ) return true ;
415+ if ( right === null && ( left == 0 || left == "" ) ) return true ;
416+
371417 if ( left === null || right === null ) {
372418 if ( operator === '=' || operator === '==' ) return left === right ;
373419 if ( operator === '<>' || operator === '!=' ) return left !== right ;
0 commit comments