This is an automated email from the ASF dual-hosted git repository.

yamer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new ea1282cd63 Decision Engine: Handling Question mark Operator in 
decision table unary test expressions (#6577)
ea1282cd63 is described below

commit ea1282cd635f1c6db4ef527b5eb5f23b42011f7b
Author: ChinchuAjith <[email protected]>
AuthorDate: Wed Feb 18 14:44:45 2026 +0530

    Decision Engine: Handling Question mark Operator in decision table unary 
test expressions (#6577)
    
    * Handling ? operator in decision table expression
    
    * Code change to improve performance
    
    * Removing cached implementation as it is not thread safe
    
    * avoids redundant re‑evaluation by implementing map
    
    * adding unimplemented methods
    
    * Review comments fix
    
    * Review comments fix
    
    * Comments updated
    
    * Fixing review comments
    
    * Fixing review comments
    
    * Removing unused imports
    
    * Unit test cases
---
 .../org/kie/dmn/feel/lang/ast/UnaryTestNode.java   | 123 ++++++++++++++-------
 .../kie/dmn/feel/lang/ast/UnaryTestNodeTest.java   | 108 ++++++++++++++++++
 2 files changed, 192 insertions(+), 39 deletions(-)

diff --git 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/UnaryTestNode.java
 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/UnaryTestNode.java
index 79a5e757c7..25a2c2e3c3 100644
--- 
a/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/UnaryTestNode.java
+++ 
b/kie-dmn/kie-dmn-feel/src/main/java/org/kie/dmn/feel/lang/ast/UnaryTestNode.java
@@ -21,6 +21,7 @@ package org.kie.dmn.feel.lang.ast;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Objects;
 
 import org.antlr.v4.runtime.ParserRuleContext;
 import org.kie.dmn.api.feel.runtime.events.FEELEvent.Severity;
@@ -119,23 +120,9 @@ public class UnaryTestNode
 
     public UnaryTest getUnaryTest() {
         return new UnaryTestImpl((context, left) -> {
-            Object right = value.evaluate(context);
             DialectHandler handler = DialectHandlerFactory.getHandler(context);
 
-            Object result;
             switch (operator) {
-                case LTE:
-                    result = handler.executeLte(left, right, context);
-                    break;
-                case LT:
-                    result = handler.executeLt(left, right, context);
-                    break;
-                case GT:
-                    result = handler.executeGt(left, right, context);
-                    break;
-                case GTE:
-                    result = handler.executeGte(left, right, context);
-                    break;
                 case EQ:
                     return createIsEqualUnaryTest().apply(context, left);
                 case NE:
@@ -146,12 +133,25 @@ public class UnaryTestNode
                     return createNotUnaryTest().apply(context, left);
                 case TEST:
                     return createBooleanUnaryTest().apply(context, left);
+                    
+                case LTE:
+                case LT:
+                case GT:
+                case GTE:
+                    // Comparison operators share the same right value 
evaluation
+                    Object right = evaluateRightValue(context, left);
+                    Object result = switch (operator) {
+                        case LTE -> handler.executeLte(left, right, context);
+                        case LT -> handler.executeLt(left, right, context);
+                        case GT -> handler.executeGt(left, right, context);
+                        case GTE -> handler.executeGte(left, right, context);
+                        default -> throw new 
UnsupportedOperationException("Unsupported operator: " + operator);
+                    };
+                    return (result instanceof Boolean) ? (Boolean) result : 
Boolean.FALSE;
 
                 default:
                     throw new UnsupportedOperationException("Unsupported 
operator: " + operator);
             }
-
-            return (result instanceof Boolean) ? (Boolean) result : 
Boolean.FALSE;
         }, value.getText());
     }
 
@@ -224,37 +224,82 @@ public class UnaryTestNode
                         () -> Boolean.FALSE)
         );
     }
+    Object evaluateRightValue(EvaluationContext context, Object left) {
+        Object right;
+        // set the value if the expression contains ('?') question mark
+        if (containsQuestionMarkReference(value)) {
+            Object existing = context.getValue("?");
+            if (Objects.equals(existing, left)) {
+                right = value.evaluate(context);
+            } else {
+                context.enterFrame();
+                try {
+                    context.setValue("?", left);
+                    right = value.evaluate(context);
+                } finally {
+                    context.exitFrame();
+                }
+            }
+        } else {
+            right = value.evaluate(context);
+        }
+        return right;
+    }
+    
+    /**
+     * Checks if the given node is a plain '?'
+     */
+    private boolean isPlainQuestionMark(BaseNode node) {
+        return node instanceof NameRefNode && "?".equals(((NameRefNode) 
node).getText());
+    }
+    
+    /**
+     * Recursively checks if a BaseNode or its children contain a reference to 
'?'
+     */
+    private boolean containsQuestionMarkReference(BaseNode node) {
+        if (isPlainQuestionMark(node)) {
+            return true;
+        }
+        if (node.getChildrenNode() != null) {
+            for (ASTNode child : node.getChildrenNode()) {
+                if (child instanceof BaseNode && 
containsQuestionMarkReference((BaseNode) child)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 
     private UnaryTest createIsEqualUnaryTest() {
         return (context, left) -> {
-            Object right = value.evaluate(context);
+            Object right = evaluateRightValue(context, left);
             return utEqualSemantic(left, right);
         };
     }
 
     private UnaryTest createIsNotEqualUnaryTest() {
         return (context, left) -> {
-            Object right = value.evaluate(context);
+            Object right = evaluateRightValue(context, left);
             Boolean result = utEqualSemantic(left, right);
             return result != null ? !result : null;
         };
     }
 
     private UnaryTest createInUnaryTest() {
-        return (c, o) -> {
-            if (o == null) {
+        return (context, left) -> {
+            if (left == null) {
                 return false;
             }
-            Object val = value.evaluate(c);
-            if (val instanceof Range) {
+            Object right = evaluateRightValue(context, left);
+            if (right instanceof Range) {
                 try {
-                    return ((Range) val).includes(c, o);
+                    return ((Range) right).includes(context, left);
                 } catch (Exception e) {
-                    c.notifyEvt(astEvent(Severity.ERROR, 
Msg.createMessage(Msg.EXPRESSION_IS_RANGE_BUT_VALUE_IS_NOT_COMPARABLE, o, 
val)));
+                    context.notifyEvt(astEvent(Severity.ERROR, 
Msg.createMessage(Msg.EXPRESSION_IS_RANGE_BUT_VALUE_IS_NOT_COMPARABLE, left, 
right)));
                     throw e;
                 }
-            } else if (val instanceof Collection) {
-                return ((Collection) val).contains(o);
+            } else if (right instanceof Collection) {
+                return ((Collection) right).contains(left);
             } else {
                 return false; // make consistent with #createNotUnaryTest()
             }
@@ -262,39 +307,39 @@ public class UnaryTestNode
     }
 
     private UnaryTest createNotUnaryTest() {
-        return (c, o) -> {
-            Object val = value.evaluate(c);
-            if (val == null) {
+        return (context, left) -> {
+            Object right = evaluateRightValue(context, left);
+            if (right == null) {
                 return null;
             }
-            List<Object> tests = (List<Object>) val;
+            List<Object> tests = (List<Object>) right;
             for (Object test : tests) {
                 if (test == null) {
-                    if (o == null) {
+                    if (left == null) {
                         return false;
                     }
                 } else if (test instanceof UnaryTest) {
-                    if (((UnaryTest) test).apply(c, o)) {
+                    if (((UnaryTest) test).apply(context, left)) {
                         return false;
                     }
-                } else if (o == null) {
+                } else if (left == null) {
                     if (test == null) {
                         return false;
                     }
                 } else if (test instanceof Range) {
                     try {
-                        if (((Range) test).includes(c, o)) {
+                        if (((Range) test).includes(context, left)) {
                             return false;
                         }
                     } catch (Exception e) {
-                        c.notifyEvt(astEvent(Severity.ERROR, 
Msg.createMessage(Msg.EXPRESSION_IS_RANGE_BUT_VALUE_IS_NOT_COMPARABLE, o, 
test)));
+                        context.notifyEvt(astEvent(Severity.ERROR, 
Msg.createMessage(Msg.EXPRESSION_IS_RANGE_BUT_VALUE_IS_NOT_COMPARABLE, left, 
test)));
                         throw e;
                     }
                 } else if (test instanceof Collection) {
-                    return !((Collection) test).contains(o);
+                    return !((Collection) test).contains(left);
                 } else {
-                    // test is a constant, so return false if it is equal to 
"o"
-                    if (test.equals(o)) {
+                    // test is a constant, so return false if it is equal to 
"left"
+                    if (test.equals(left)) {
                         return false;
                     }
                 }
@@ -305,7 +350,7 @@ public class UnaryTestNode
 
     private UnaryTest createBooleanUnaryTest() {
         return (context, left) -> {
-            Object right = value.evaluate(context);
+            Object right = evaluateRightValue(context, left);
             if (right instanceof Boolean) {
                 return (Boolean) right;
             } else {
diff --git 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/UnaryTestNodeTest.java
 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/UnaryTestNodeTest.java
index fcd2b205cc..bcb1e6157a 100644
--- 
a/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/UnaryTestNodeTest.java
+++ 
b/kie-dmn/kie-dmn-feel/src/test/java/org/kie/dmn/feel/lang/ast/UnaryTestNodeTest.java
@@ -18,14 +18,22 @@
  */
 package org.kie.dmn.feel.lang.ast;
 
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.stream.Stream;
 
+import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.Arguments;
 import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.dmn.feel.lang.EvaluationContext;
+import org.kie.dmn.feel.lang.FEELDialect;
+import org.kie.dmn.feel.lang.impl.EvaluationContextImpl;
+import org.kie.dmn.feel.lang.impl.FEELEventListenersManager;
+import org.kie.dmn.feel.lang.types.BuiltInType;
+import org.kie.dmn.feel.util.ClassLoaderUtil;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -93,4 +101,104 @@ class UnaryTestNodeTest {
             Arguments.of(null, 42, false, "Left null, right non-null")
         );
     }
+
+    /**
+     * Tests for evaluateRightValue method - without question mark
+     */
+    
+    @Test
+    void testEvaluateRightValue_SimpleValue_WithoutQuestionMark() {
+        NumberNode valueNode = new NumberNode(BigDecimal.valueOf(42), "42");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, valueNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(100);
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(42));
+    }
+
+    @Test
+    void testEvaluateRightValue_WithQuestionMark_NotInContext() {
+        NameRefNode questionMarkNode = new NameRefNode(BuiltInType.UNKNOWN, 
"?");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, questionMarkNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(123);
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(123));
+    }
+
+    @Test
+    void testEvaluateRightValue_WithQuestionMark_AlreadyInContext_SameValue() {
+        NameRefNode questionMarkNode = new NameRefNode(BuiltInType.UNKNOWN, 
"?");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, questionMarkNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(123);
+        context.setValue("?", left); // '?' already set to same value
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(123));
+    }
+
+    @Test
+    void 
testEvaluateRightValue_WithQuestionMark_AlreadyInContext_DifferentValue() {
+        NameRefNode questionMarkNode = new NameRefNode(BuiltInType.UNKNOWN, 
"?");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, questionMarkNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(123);
+        context.setValue("?", BigDecimal.valueOf(999)); // '?' set to 
different value
+
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(123));
+        assertThat(context.getValue("?")).isEqualTo(BigDecimal.valueOf(999));
+    }
+
+    @Test
+    void testEvaluateRightValue_WithQuestionMarkInExpression() {
+        NameRefNode questionMarkNode = new NameRefNode(BuiltInType.UNKNOWN, 
"?");
+        NumberNode tenNode = new NumberNode(BigDecimal.valueOf(10), "10");
+        InfixOpNode additionNode = new InfixOpNode(InfixOperator.ADD, 
questionMarkNode, tenNode, "? + 10");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, additionNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(5);
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(15));
+    }
+
+    @Test
+    void testEvaluateRightValue_FrameCleanup() {
+        NameRefNode questionMarkNode = new NameRefNode(BuiltInType.UNKNOWN, 
"?");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, questionMarkNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        context.setValue("originalVar", "originalValue");
+        Object left = BigDecimal.valueOf(123);
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo(BigDecimal.valueOf(123));
+        assertThat(context.getValue("originalVar")).isEqualTo("originalValue");
+    }
+
+    @Test
+    void testEvaluateRightValue_WithNull() {
+        NullNode nullNode = new NullNode("null");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, nullNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = BigDecimal.valueOf(123);
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isNull();
+    }
+
+    @Test
+    void testEvaluateRightValue_WithString() {
+        StringNode stringNode = new StringNode("hello");
+        UnaryTestNode unaryTestNode = new 
UnaryTestNode(UnaryTestNode.UnaryOperator.EQ, stringNode);
+        
+        EvaluationContext context = new 
EvaluationContextImpl(ClassLoaderUtil.findDefaultClassLoader(), new 
FEELEventListenersManager(), FEELDialect.FEEL);
+        Object left = "world";
+        Object result = unaryTestNode.evaluateRightValue(context, left);
+        assertThat(result).isEqualTo("hello");
+    }
 }
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to