This is an automated email from the ASF dual-hosted git repository.
tkobayas 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 f47946f1d1 Enhance ConstraintOperator capability (#6104)
f47946f1d1 is described below
commit f47946f1d1cc6fef52cfd1cd1fda867efa9b3583
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Tue Oct 1 12:21:49 2024 +0900
Enhance ConstraintOperator capability (#6104)
* [DROOLS-7635] ansible-rulebook : Raise an error when a condition compares
incompatible types
* - Add beta index support
- Add unit test
* changed method names
---
.../java/org/drools/model/ConstraintOperator.java | 8 +
.../execmodel/CustomConstraintOperatorTest.java | 254 +++++++++++++++++++++
.../org/drools/model/prototype/PrototypeDSL.java | 24 +-
3 files changed, 280 insertions(+), 6 deletions(-)
diff --git
a/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java
b/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java
index 6f216edd44..dd96c929fa 100644
---
a/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java
+++
b/drools-model/drools-canonical-model/src/main/java/org/drools/model/ConstraintOperator.java
@@ -22,4 +22,12 @@ import java.util.function.BiPredicate;
public interface ConstraintOperator {
<T, V> BiPredicate<T, V> asPredicate();
+
+ default boolean hasIndex() {
+ return false;
+ }
+
+ default Index.ConstraintType getIndexType() {
+ throw new UnsupportedOperationException();
+ }
}
diff --git
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java
new file mode 100644
index 0000000000..d1b5c6f776
--- /dev/null
+++
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/CustomConstraintOperatorTest.java
@@ -0,0 +1,254 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.model.codegen.execmodel;
+
+import java.util.function.BiPredicate;
+
+import org.drools.base.common.NetworkNode;
+import org.drools.base.prototype.PrototypeObjectType;
+import org.drools.base.rule.IndexableConstraint;
+import org.drools.core.reteoo.AlphaNode;
+import org.drools.core.reteoo.BetaNode;
+import org.drools.core.reteoo.EntryPointNode;
+import org.drools.core.reteoo.ObjectTypeNode;
+import org.drools.kiesession.rulebase.InternalKnowledgeBase;
+import org.drools.model.ConstraintOperator;
+import org.drools.model.Index;
+import org.drools.model.Model;
+import org.drools.model.Rule;
+import org.drools.model.codegen.execmodel.domain.Result;
+import org.drools.model.impl.ModelImpl;
+import org.drools.model.prototype.PrototypeVariable;
+import org.drools.modelcompiler.KieBaseBuilder;
+import org.drools.modelcompiler.constraints.LambdaConstraint;
+import org.junit.Test;
+import org.kie.api.KieBase;
+import org.kie.api.prototype.PrototypeFact;
+import org.kie.api.prototype.PrototypeFactInstance;
+import org.kie.api.runtime.KieSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.drools.model.DSL.on;
+import static org.drools.model.PatternDSL.rule;
+import static org.drools.model.prototype.PrototypeDSL.protoPattern;
+import static org.drools.model.prototype.PrototypeDSL.variable;
+import static org.drools.model.prototype.PrototypeExpression.fixedValue;
+import static org.drools.model.prototype.PrototypeExpression.prototypeField;
+import static org.kie.api.prototype.PrototypeBuilder.prototype;
+
+public class CustomConstraintOperatorTest {
+
+ static class CustomConstraintOperator implements ConstraintOperator {
+
+ public int counter = 0;
+
+ @Override
+ public <T, V> BiPredicate<T, V> asPredicate() {
+ return (t, v) -> {
+ counter++;
+ return t.equals(v);
+ };
+ }
+
+ @Override
+ public boolean hasIndex() {
+ return true;
+ }
+
+ @Override
+ public Index.ConstraintType getIndexType() {
+ return Index.ConstraintType.EQUAL;
+ }
+
+ @Override
+ public String toString() {
+ return Index.ConstraintType.EQUAL.toString();
+ }
+ }
+
+ @Test
+ public void alphaIndexIneffective() {
+ CustomConstraintOperator customConstraintOperator = new
CustomConstraintOperator();
+
+ PrototypeFact testPrototype = prototype("test").asFact();
+ PrototypeVariable testV = variable(testPrototype);
+
+ Rule rule1 = rule("alpha1")
+ .build(
+ protoPattern(testV)
+ .expr(prototypeField("fieldA"),
customConstraintOperator, fixedValue(1)),
+ on(testV).execute((drools, x) ->
+ drools.insert(new
Result("Found"))
+ )
+ );
+
+ Model model = new ModelImpl().addRule(rule1);
+ KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model);
+ KieSession ksession = kieBase.newKieSession();
+
+ PrototypeFactInstance testFact = testPrototype.newInstance();
+ testFact.put("fieldA", 1);
+
+ ksession.insert(testFact);
+ assertThat(ksession.fireAllRules()).isEqualTo(1);
+
+ // Index is created, but actual alpha index hashing works only with
more than 3 nodes
+ Index index = getFirstAlphaNodeIndex((InternalKnowledgeBase) kieBase,
testPrototype);
+ assertThat(index.getIndexType()).isEqualTo(Index.IndexType.ALPHA);
+
assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL);
+
+ // alpha index hashing is not effective, so the predicated is called
+ assertThat(customConstraintOperator.counter).isEqualTo(1);
+ }
+
+ @Test
+ public void alphaIndexEffective() {
+ CustomConstraintOperator customConstraintOperator = new
CustomConstraintOperator();
+
+ PrototypeFact testPrototype = prototype("test").asFact();
+ PrototypeVariable testV = variable(testPrototype);
+
+ Rule rule1 = rule("alpha1")
+ .build(
+ protoPattern(testV)
+ .expr(prototypeField("fieldA"),
customConstraintOperator, fixedValue(1)),
+ on(testV).execute((drools, x) ->
+ drools.insert(new
Result("Found"))
+ )
+ );
+ Rule rule2 = rule("alpha2")
+ .build(
+ protoPattern(testV)
+ .expr(prototypeField("fieldA"),
customConstraintOperator, fixedValue(2)),
+ on(testV).execute((drools, x) ->
+ drools.insert(new
Result("Found"))
+ )
+ );
+ Rule rule3 = rule("alpha3")
+ .build(
+ protoPattern(testV)
+ .expr(prototypeField("fieldA"),
customConstraintOperator, fixedValue(3)),
+ on(testV).execute((drools, x) ->
+ drools.insert(new
Result("Found"))
+ )
+ );
+
+ Model model = new
ModelImpl().addRule(rule1).addRule(rule2).addRule(rule3);
+ KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model);
+ KieSession ksession = kieBase.newKieSession();
+
+ PrototypeFactInstance testFact = testPrototype.newInstance();
+ testFact.put("fieldA", 1);
+
+ ksession.insert(testFact);
+ assertThat(ksession.fireAllRules()).isEqualTo(1);
+
+ Index index = getFirstAlphaNodeIndex((InternalKnowledgeBase) kieBase,
testPrototype);
+ assertThat(index.getIndexType()).isEqualTo(Index.IndexType.ALPHA);
+
assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL);
+
+ // alpha index hashing is effective, so the predicated is not called
+ assertThat(customConstraintOperator.counter).isZero();
+ }
+
+ private static Index getFirstAlphaNodeIndex(InternalKnowledgeBase kieBase,
PrototypeFact testPrototype) {
+ EntryPointNode epn =
kieBase.getRete().getEntryPointNodes().values().iterator().next();
+ ObjectTypeNode otn = epn.getObjectTypeNodes().get(new
PrototypeObjectType(testPrototype));
+ AlphaNode alphaNode = (AlphaNode)
otn.getObjectSinkPropagator().getSinks()[0];
+ IndexableConstraint constraint = (IndexableConstraint)
alphaNode.getConstraint();
+ return ((LambdaConstraint) constraint).getEvaluator().getIndex();
+ }
+
+ @Test
+ public void betaIndex() {
+ CustomConstraintOperator customConstraintOperator = new
CustomConstraintOperator();
+
+ Result result = new Result();
+
+ PrototypeFact personFact =
prototype("org.drools.Person").withField("name").withField("age").asFact();
+
+ PrototypeVariable markV = variable(personFact);
+ PrototypeVariable ageMateV = variable(personFact);
+
+ Rule rule = rule("beta")
+ .build(
+ protoPattern(markV)
+ .expr("name", Index.ConstraintType.EQUAL,
"Mark"),
+ protoPattern(ageMateV)
+ .expr("name", Index.ConstraintType.NOT_EQUAL,
"Mark")
+ .expr("age", customConstraintOperator, markV,
"age"),
+ on(ageMateV, markV).execute((p1, p2) ->
result.setValue(p1.get("name") + " is the same age as " + p2.get("name")))
+ );
+
+ Model model = new ModelImpl().addRule(rule);
+ KieBase kieBase = KieBaseBuilder.createKieBaseFromModel(model);
+
+ KieSession ksession = kieBase.newKieSession();
+
+ PrototypeFactInstance mark = personFact.newInstance();
+ mark.put("name", "Mark");
+ mark.put("age", 37);
+
+ PrototypeFactInstance john = personFact.newInstance();
+ john.put("name", "John");
+ john.put("age", 39);
+
+ PrototypeFactInstance paul = personFact.newInstance();
+ paul.put("name", "Paul");
+ paul.put("age", 37);
+
+ ksession.insert(mark);
+ ksession.insert(john);
+ ksession.insert(paul);
+
+ ksession.fireAllRules();
+ assertThat(result.getValue()).isEqualTo("Paul is the same age as
Mark");
+
+ Index index = getFirstBetaNodeIndex((InternalKnowledgeBase) kieBase,
personFact);
+ assertThat(index.getIndexType()).isEqualTo(Index.IndexType.BETA);
+
assertThat(index.getConstraintType()).isEqualTo(Index.ConstraintType.EQUAL);
+
+ // When beta index is used, the predicate in the custom operator is
not actually called
+ assertThat(customConstraintOperator.counter).isZero();
+ }
+
+ private static Index getFirstBetaNodeIndex(InternalKnowledgeBase kieBase,
PrototypeFact testPrototype) {
+ EntryPointNode epn =
kieBase.getRete().getEntryPointNodes().values().iterator().next();
+ ObjectTypeNode otn = epn.getObjectTypeNodes().get(new
PrototypeObjectType(testPrototype));
+ NetworkNode[] sinks = otn.getObjectSinkPropagator().getSinks();
+ BetaNode betaNode = findBetaNode(sinks);
+
+ IndexableConstraint constraint = (IndexableConstraint)
betaNode.getConstraints()[0];
+ return ((LambdaConstraint) constraint).getEvaluator().getIndex();
+ }
+
+ private static BetaNode findBetaNode(NetworkNode[] sinks) {
+ for (NetworkNode sink : sinks) {
+ if (sink instanceof BetaNode) {
+ return (BetaNode) sink;
+ } else {
+ BetaNode betaNode = findBetaNode(sink.getSinks());
+ if (betaNode != null) {
+ return betaNode;
+ }
+ }
+ }
+ return null;
+ }
+}
diff --git
a/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java
b/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java
index 1952375d11..265a430429 100644
---
a/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java
+++
b/drools-model/drools-model-prototype/src/main/java/org/drools/model/prototype/PrototypeDSL.java
@@ -127,16 +127,22 @@ public class PrototypeDSL {
reactOnFields.addAll(left.getImpactedFields());
reactOnFields.addAll(right.getImpactedFields());
+ // If operator is not Index.ConstraintType, it may contain
Index.ConstraintType internally for indexing purposes
+ ConstraintOperator operatorAsIndexType = operator;
+ if (operator.hasIndex()) {
+ operatorAsIndexType = operator.getIndexType();
+ }
+
expr(createExprId(left, operator, right),
asPredicate1(leftExtractor, operator,
right.asFunction(prototype)),
- createAlphaIndex(left, operator, right, prototype,
leftExtractor),
+ createAlphaIndex(left, operatorAsIndexType, right,
prototype, leftExtractor),
reactOn( reactOnFields.toArray(new
String[reactOnFields.size()])) );
return this;
}
- private static AlphaIndex createAlphaIndex(PrototypeExpression left,
ConstraintOperator operator, PrototypeExpression right, Prototype prototype,
Function1<PrototypeFactInstance, Object> leftExtractor) {
- if (left.getIndexingKey().isPresent() && right instanceof
PrototypeExpression.FixedValue && operator instanceof Index.ConstraintType
constraintType) {
+ private static AlphaIndex createAlphaIndex(PrototypeExpression left,
ConstraintOperator operatorAsIndexType, PrototypeExpression right, Prototype
prototype, Function1<PrototypeFactInstance, Object> leftExtractor) {
+ if (left.getIndexingKey().isPresent() && right instanceof
PrototypeExpression.FixedValue && operatorAsIndexType instanceof
Index.ConstraintType constraintType) {
String fieldName = left.getIndexingKey().get();
Prototype.Field field = prototype.getField(fieldName);
Object value = ((PrototypeExpression.FixedValue)
right).getValue();
@@ -167,9 +173,15 @@ public class PrototypeDSL {
reactOnFields.addAll(left.getImpactedFields());
reactOnFields.addAll(right.getImpactedFields());
+ // If operator is not Index.ConstraintType, it may contain
Index.ConstraintType internally for indexing purposes
+ ConstraintOperator operatorAsIndexType = operator;
+ if (operator.hasIndex()) {
+ operatorAsIndexType = operator.getIndexType();
+ }
+
expr(createExprId(left, operator, right),
other, asPredicate2(left.asFunction(prototype), operator,
right.asFunction(otherPrototype)),
- createBetaIndex(left, operator, right, prototype,
otherPrototype),
+ createBetaIndex(left, operatorAsIndexType, right,
prototype, otherPrototype),
reactOn( reactOnFields.toArray(new
String[reactOnFields.size()])) );
return this;
@@ -181,8 +193,8 @@ public class PrototypeDSL {
return "expr:" + leftId + ":" + operator + ":" + rightId;
}
- private BetaIndex createBetaIndex(PrototypeExpression left,
ConstraintOperator operator, PrototypeExpression right, Prototype prototype,
Prototype otherPrototype) {
- if (left.getIndexingKey().isPresent() && operator instanceof
Index.ConstraintType constraintType && right.getIndexingKey().isPresent()) {
+ private BetaIndex createBetaIndex(PrototypeExpression left,
ConstraintOperator operatorAsIndexType, PrototypeExpression right, Prototype
prototype, Prototype otherPrototype) {
+ if (left.getIndexingKey().isPresent() && operatorAsIndexType
instanceof Index.ConstraintType constraintType &&
right.getIndexingKey().isPresent()) {
String fieldName = left.getIndexingKey().get();
Prototype.Field field = prototype.getField(fieldName);
Function1<PrototypeFactInstance, Object> extractor =
left.asFunction(prototype);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]