@@ -19,12 +19,48 @@ import { painlessFieldAccessor } from '../../types/utils';
1919import { encodeValue } from '../../types/utils' ;
2020import { evaluateDateMath } from './painless_date_math_helpers' ;
2121
22- // Utility: get the field name from a filter condition
23- function safePainlessField ( conditionOrField : FilterCondition | string ) {
24- if ( typeof conditionOrField === 'string' ) {
25- return painlessFieldAccessor ( conditionOrField ) ;
22+ // Type for mapping field names to variable names
23+ type FieldVarMap = Map < string , string > ;
24+
25+ // Extract all unique field names from a condition recursively
26+ function extractFieldNames ( condition : Condition , fields : Set < string > = new Set ( ) ) : Set < string > {
27+ if ( 'field' in condition && typeof condition . field === 'string' ) {
28+ fields . add ( condition . field ) ;
29+ }
30+ if ( 'and' in condition && Array . isArray ( condition . and ) ) {
31+ condition . and . forEach ( ( c ) => extractFieldNames ( c , fields ) ) ;
32+ }
33+ if ( 'or' in condition && Array . isArray ( condition . or ) ) {
34+ condition . or . forEach ( ( c ) => extractFieldNames ( c , fields ) ) ;
2635 }
27- return painlessFieldAccessor ( conditionOrField . field ) ;
36+ if ( 'not' in condition && condition . not ) {
37+ extractFieldNames ( condition . not , fields ) ;
38+ }
39+ return fields ;
40+ }
41+
42+ // Convert a field name to a valid Painless variable name
43+ function fieldToVarName ( field : string ) : string {
44+ // Replace special characters with underscores and prefix with 'val_'
45+ return 'val_' + field . replace ( / [ ^ a - z A - Z 0 - 9 ] / g, '_' ) ;
46+ }
47+
48+ // Generate variable declaration with single-element List unwrapping
49+ function generateFieldDeclaration ( field : string , varName : string ) : string {
50+ return `def ${ varName } = $('${ field } ', null); if (${ varName } instanceof List && ${ varName } .size() == 1) { ${ varName } = ${ varName } [0]; }` ;
51+ }
52+
53+ // Utility: get the field accessor - uses varMap if provided, otherwise inline accessor
54+ function safePainlessField ( conditionOrField : FilterCondition | string , varMap ?: FieldVarMap ) {
55+ const fieldName =
56+ typeof conditionOrField === 'string' ? conditionOrField : conditionOrField . field ;
57+
58+ // If we have a varMap and it contains this field, use the variable name
59+ if ( varMap && varMap . has ( fieldName ) ) {
60+ return varMap . get ( fieldName ) ! ;
61+ }
62+
63+ return painlessFieldAccessor ( fieldName ) ;
2864}
2965
3066function generateRangeComparisonClauses (
@@ -64,8 +100,11 @@ function generateRangeComparisonClauses(
64100}
65101
66102// Convert a shorthand binary filter condition to painless
67- function shorthandBinaryToPainless ( condition : ShorthandBinaryFilterCondition ) {
68- const safeFieldAccessor = safePainlessField ( condition ) ;
103+ function shorthandBinaryToPainless (
104+ condition : ShorthandBinaryFilterCondition ,
105+ varMap ?: FieldVarMap
106+ ) {
107+ const safeFieldAccessor = safePainlessField ( condition , varMap ) ;
69108 // Find which operator is present
70109 const op = BINARY_OPERATORS . find ( ( k ) => condition [ k ] !== undefined ) ;
71110
@@ -161,12 +200,12 @@ function shorthandBinaryToPainless(condition: ShorthandBinaryFilterCondition) {
161200}
162201
163202// Convert a shorthand unary filter condition to painless
164- function shorthandUnaryToPainless ( condition : ShorthandUnaryFilterCondition ) {
203+ function shorthandUnaryToPainless ( condition : ShorthandUnaryFilterCondition , varMap ?: FieldVarMap ) {
165204 if ( 'exists' in condition ) {
166205 if ( typeof condition . exists === 'boolean' ) {
167206 return condition . exists
168- ? `${ safePainlessField ( condition ) } !== null`
169- : `${ safePainlessField ( condition ) } == null` ;
207+ ? `${ safePainlessField ( condition , varMap ) } !== null`
208+ : `${ safePainlessField ( condition , varMap ) } == null` ;
170209 } else {
171210 throw new Error ( 'Invalid value for exists operator, expected boolean' ) ;
172211 }
@@ -176,27 +215,36 @@ function shorthandUnaryToPainless(condition: ShorthandUnaryFilterCondition) {
176215}
177216
178217// Main recursive conversion to painless
179- export function conditionToStatement ( condition : Condition , nested = false ) : string {
218+ export function conditionToStatement (
219+ condition : Condition ,
220+ nested = false ,
221+ varMap ?: FieldVarMap
222+ ) : string {
180223 if ( 'field' in condition && typeof condition . field === 'string' ) {
181224 // Shorthand unary
182225 if ( 'exists' in condition ) {
183- return shorthandUnaryToPainless ( condition as ShorthandUnaryFilterCondition ) ;
226+ return shorthandUnaryToPainless ( condition as ShorthandUnaryFilterCondition , varMap ) ;
184227 }
185228 // Shorthand binary
186- return `(${ safePainlessField ( condition ) } !== null && ${ shorthandBinaryToPainless (
187- condition as ShorthandBinaryFilterCondition
229+ return `(${ safePainlessField ( condition , varMap ) } !== null && ${ shorthandBinaryToPainless (
230+ condition as ShorthandBinaryFilterCondition ,
231+ varMap
188232 ) } )`;
189233 }
190234 if ( 'and' in condition && Array . isArray ( condition . and ) ) {
191- const and = condition . and . map ( ( filter ) => conditionToStatement ( filter , true ) ) . join ( ' && ' ) ;
235+ const and = condition . and
236+ . map ( ( filter ) => conditionToStatement ( filter , true , varMap ) )
237+ . join ( ' && ' ) ;
192238 return nested ? `(${ and } )` : and ;
193239 }
194240 if ( 'or' in condition && Array . isArray ( condition . or ) ) {
195- const or = condition . or . map ( ( filter ) => conditionToStatement ( filter , true ) ) . join ( ' || ' ) ;
241+ const or = condition . or
242+ . map ( ( filter ) => conditionToStatement ( filter , true , varMap ) )
243+ . join ( ' || ' ) ;
196244 return nested ? `(${ or } )` : or ;
197245 }
198246 if ( 'not' in condition && condition . not ) {
199- return `!(${ conditionToStatement ( condition . not , true ) } )` ;
247+ return `!(${ conditionToStatement ( condition . not , true , varMap ) } )` ;
200248 }
201249 // Always/never conditions (if you have them)
202250 if ( 'always' in condition ) {
@@ -217,9 +265,27 @@ export function conditionToPainless(condition: Condition): string {
217265 return `return true` ;
218266 }
219267
268+ // Extract all field names and create variable mappings
269+ const fields = extractFieldNames ( condition ) ;
270+ const varMap : FieldVarMap = new Map ( ) ;
271+ const declarations : string [ ] = [ ] ;
272+
273+ for ( const field of fields ) {
274+ const varName = fieldToVarName ( field ) ;
275+ varMap . set ( field , varName ) ;
276+ declarations . push ( generateFieldDeclaration ( field , varName ) ) ;
277+ }
278+
279+ // declarationsBlock will look like this:
280+ // def val_field1 = $('field1', null); if (val_field1 instanceof List && val_field1.size() == 1) { val_field1 = val_field1[0]; }
281+ const declarationsBlock = declarations . length > 0 ? declarations . join ( '\n ' ) + '\n ' : '' ;
282+
220283 return `
221284 try {
222- if (${ conditionToStatement ( condition ) } ) {
285+
286+ ${ declarationsBlock }
287+
288+ if (${ conditionToStatement ( condition , false , varMap ) } ) {
223289 return true;
224290 }
225291 return false;
0 commit comments