Unnecessary rule consequence triggering from accumulate

Viewed 65

Note: A better question is written about this topic. Please vote for this one to be closed.

Link to new question

I ran into a weird case which i'm trying to understand why it happens. The issue is where a rule is constantly triggering it's consequence for it's last fact, without anything related to it being changed. I should note that i am using drools 7.0.0

Behavior analysis

I have the following rules:

rule "TicketsBoughtPerClass"
when
    $ticketClass : TicketClass($customer : customer)
    accumulate(
        Ticket
        (
            customer != null,
            customer == $customer,
            ticketPrice >= $ticketClass.startPriceRange,
            ticketPrice <= $ticketClass.endPriceRange
        ); 
        $ticketCount : sum(1)
    )
then
    System.out.println("Total " + $ticketCount + " bought tickets for " + $ticketClass.getClassName());
    insertLogical(new TotalTicketsBoughtForClass($ticketClass, $ticketCount));
end

rule "TicketsNeededForBonus"
when
    $ticketClass : TicketClass($minTicketsNeededForBonus : minTicketsNeededForBonus)
    TotalTicketsBoughtForClass(ticketClass == $ticketClass, ticketCount < $minTicketsNeededForBonus, $ticketCount : ticketCount)
then
    //Do something based on ($minTicketsNeededForBonus - $ticketCount)
end

The idea is to count the number of Ticket objects that are within a price range of a TicketClass for a Customer. However, as i mentioned, regardless of the price of the Ticket, there is always a rule trigger for the last inserted fact.

I added <- no match for the triggers of interest.

Here is a sample output:

Ticket classes (inserting ticketClass facts): 
First class - Start price range: 200, End price range: 300
Second class - Start price range: 100, End price range: 199
Third class - Start price range: 50, End price range: 99

Buying tickets:
Bought ticket #0 for 60$ (insert)
Bought ticket #1 for 199$ (insert)
Bought ticket #2 for 250$ (insert)

Calling initial fireAllRules()
Total 1 bought tickets for Third class
Total 1 bought tickets for Second class
Total 1 bought tickets for First class

Changed ticket #0 from 60$ to 168$ (update)
fireAllRules() called
Total 0 bought tickets for Third class
Total 2 bought tickets for Second class

Changed ticket #0 from 168$ to 233$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match
Total 1 bought tickets for Second class
Total 2 bought tickets for First class

Changed ticket #0 from 233$ to 230$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match
Total 2 bought tickets for First class

Changed ticket #0 from 230$ to 283$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match
Total 2 bought tickets for First class

Changed ticket #0 from 283$ to 167$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match
Total 2 bought tickets for Second class
Total 1 bought tickets for First class

Changed ticket #0 from 167$ to 24$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match
Total 1 bought tickets for Second class

Changed ticket #0 from 24$ to 1$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match

Changed ticket #0 from 1$ to 0$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match

Changed ticket #0 from 0$ to 8$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match

Changed ticket #0 from 8$ to 40$ (update)
fireAllRules() called
Total 0 bought tickets for Third class <- no match

Unless there is something i have misunderstood, this should not be the case.

Debugging

I decided to dig in a little deeper into the drools core to get some answers myself. The search led me to the evaluation of the updated tuples in the accumulate node in PhreakAccumulateNode.doRightUpdates, which is where this additional trigger seems to be coming from. Just before the method doRightUpdatesProcessChildren, the following code is present:

// if LeftTupleMemory is empty, there are no matches to modify
if ( leftTuple != null ) {
   if ( leftTuple.getStagedType() == LeftTuple.NONE ) {
         trgLeftTuples.addUpdate( leftTuple ); //<----
   }
   doRightUpdatesProcessChildren( ARGS );
}

In short, this code always adds the first left tuple that is in AccmulateMemory as being updated, even though it isn't, which is what causes the RuleTerminalNode to always fire it's consequence for this tuple.

My current dilemma is that I don't understand if this call for trgLeftTuples.addUpdate( leftTuple ) is done on purpose, because when I took a look at the code in PhreakJoinNode, extremely similar code is present when processing the updated tuples, but this call is missing before doRightUpdatesProcessChildren is called.

The main question i have about this if it is the expected behavior, and if so, why?.

1 Answers

If I got you right, and you want to eliminate rule triggering when there were found no single ticket for your rule, then you should add additional constraint.

rule "TicketsBoughtPerClass"
when
    $ticketClass : TicketClass($customer : customer)
    accumulate(
        Ticket
        (
            customer != null,
            customer == $customer,
            ticketPrice >= $ticketClass.startPriceRange,
            ticketPrice <= $ticketClass.endPriceRange
        );
        $ticketCount : sum(1);
        $ticketCount > 0
    )
then
    System.out.println("Total " + $ticketCount + " bought tickets for " + $ticketClass.getClass().getName());
end

Without $ticketCount > 0 constraint, you have the only constraint for rule to be triggered TicketClass()

Related