hyperjumptech/grule-rule-engine

When an external function is called with exactly the same parameters only one rule is executed although Log tell that the rule is called

janmpo opened this issue · 7 comments

janmpo commented

Describe the bug
I have tried to make an easy external function example with a function GetMult(X) that only multiplies the value from struct by X (MyFact.N * X).

If you execute the code below with 4 simple rules, the first rule copies the value M to N and the result N, once the other 3 rules are executed, must be M(2000) * 2 * 3 * 2= 24000, however the rule system returns 4000.

If you modify the rule (rule attached at the end)

  • CheckB GetMult is set to 2
  • CheckCD GetMult is set to 3

the result must be M(2000) * 2 * 2 * 3 = 24000 but 12000 is returned (in the previous case was 4000 and it doesn't matter the order of multipliers)

To Reproduce
Steps to reproduce the behavior:

  1. Check this code:
package main

import (
	"fmt"
	"log"

	"github.com/hyperjumptech/grule-rule-engine/ast"
	"github.com/hyperjumptech/grule-rule-engine/builder"
	"github.com/hyperjumptech/grule-rule-engine/engine"
	"github.com/hyperjumptech/grule-rule-engine/pkg"
)

type MyFact struct {
	A string
	B string
	C string
	D string
	M float64
	N float64
}

func main() {
	kb := ast.NewKnowledgeLibrary()
	rb := builder.NewRuleBuilder(kb)

	drls := `
	rule SetN "Sets N" salience 20 {
		when
			MF.N == 0
		then
			MF.N = MF.M;
			Retract("SetN");
			Log("Set N called");
	}
	rule CheckA "Check A" salience 11 {
		when
			MF.A == "A"
		then
			MF.N = MF.GetMult(2);
			Retract("CheckA");
			Log("Check A called");
	}
	rule CheckB "Check B" salience 12 {
		when
			MF.B == "B"
		then
			MF.N = MF.GetMult(3);
			Retract("CheckB");
			Log("Check B called");
	}
	rule CheckCD "Check C&D" salience 13 {
		when
			MF.C == "C" && MF.D == "D"
		then
			MF.N = MF.GetMult(2);
			Retract("CheckCD");
			Log("Check C&D called");
	}`

	myFact := &MyFact{
		A: "A",
		B: "B",
		C: "C",
		D: "D",
		M: 2000,
	}

	dataCtx := ast.NewDataContext()
	err := dataCtx.Add("MF", myFact)
	if err != nil {
		panic(err)
	}

	// Add the rule definition above into the library and name it 'TutorialRules'  version '0.0.1'
	bs := pkg.NewBytesResource([]byte(drls))
	err = rb.BuildRuleFromResource("TutorialRules", "0.0.1", bs)
	if err != nil {
		panic(err)
	}

	knowledgeBase, _ := kb.NewKnowledgeBaseInstance("TutorialRules", "0.0.1")

	engine := engine.NewGruleEngine()
	err = engine.Execute(dataCtx, knowledgeBase)
	if err != nil {
		panic(err)
	}

	// Expected myFact.N 2000*2*3*2 = 24000
	fmt.Println("Final VALUE: ", myFact.N)
}

func (mf *MyFact) GetMult(factor int64) float64 {
	log.Println("------------------")
	log.Println("GetMult Called. Returned Value: ", float64(factor)*mf.N)
	log.Println("------------------")
	return float64(factor) * mf.N
	// pricedFlight.PersonalizedPrice = float32(multiplier) * pricedFlight.PersonalizedPrice

}
  1. The returned value is 4000 (or 12000 if you modified as described). Output:
INFO[0000] Set N called                                  lib=grule-rule-engine package=AST source=GRL
------------------
GetMult Called. Returned value should be:  4000
------------------
INFO[0000] Check C&D called                              lib=grule-rule-engine package=AST source=GRL
------------------
GetMult Called. Returned value should be:  12000
------------------
INFO[0000] Check B called                                lib=grule-rule-engine package=AST source=GRL
INFO[0000] Check A called                                lib=grule-rule-engine package=AST source=GRL
Final VALUE:  4000

  1. Real value should be in all the cases 24000
  2. As you will see in the log all the rules are called but the function GetMult is not invoked, and even when it is the func called the value is not stored.

Expected behavior
The function must be called and executed correctly no matter whether we call the same function with the same parameters in different rules.

Additional context
This is the drl for the second scenario:

	drls := `
	rule SetN "Sets N" salience 20 {
		when
			MF.N == 0
		then
			MF.N = MF.M;
			Retract("SetN");
			Log("Set N called");
	}
	rule CheckA "Check A" salience 11 {
		when
			MF.A == "A"
		then
			MF.N = MF.GetMult(2);
			Retract("CheckA");
			Log("Check A called");
	}
	rule CheckB "Check B" salience 12 {
		when
			MF.B == "B"
		then
			MF.N = MF.GetMult(2);
			Retract("CheckB");
			Log("Check B called");
	}
	rule CheckCD "Check C&D" salience 13 {
		when
			MF.C == "C" && MF.D == "D"
		then
			MF.N = MF.GetMult(3);
			Retract("CheckCD");
			Log("Check C&D called");
	}`

However if the arguments are different i.e. GetMult(2), GetMult(3) & GetMult(5) the result is correct (60000).

Thanks for your support.

janmpo commented

Sorry I forgot to add go.mod dependencies

module grule

go 1.21.3

require github.com/hyperjumptech/grule-rule-engine v1.14.1

require (
	github.com/Microsoft/go-winio v0.5.2 // indirect
	github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect
	github.com/bmatcuk/doublestar v1.3.4 // indirect
	github.com/emirpasic/gods v1.18.1 // indirect
	github.com/google/uuid v1.4.0 // indirect
	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
	github.com/kevinburke/ssh_config v1.2.0 // indirect
	github.com/mitchellh/go-homedir v1.1.0 // indirect
	github.com/sergi/go-diff v1.3.1 // indirect
	github.com/sirupsen/logrus v1.9.3 // indirect
	github.com/src-d/gcfg v1.4.0 // indirect
	github.com/xanzy/ssh-agent v0.3.3 // indirect
	go.uber.org/multierr v1.11.0 // indirect
	go.uber.org/zap v1.26.0 // indirect
	golang.org/x/crypto v0.15.0 // indirect
	golang.org/x/net v0.18.0 // indirect
	golang.org/x/sys v0.14.0 // indirect
	gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
	gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
	gopkg.in/warnings.v0 v0.1.2 // indirect
)

janmpo commented

Hi,

This is driving me crazy... Please if someone copy, paste & run the code and works correctly, just drop me a message...

Thanks

janmpo

2024/01/04 15:11:51 GetMult Called. Returned Value:  4000
2024/01/04 15:11:51 ------------------
INFO[0000] Check C&D called                              lib=grule-rule-engine package=AST source=GRL
2024/01/04 15:11:51 ------------------
2024/01/04 15:11:51 GetMult Called. Returned Value:  12000
2024/01/04 15:11:51 ------------------
INFO[0000] Check B called                                lib=grule-rule-engine package=AST source=GRL
INFO[0000] Check A called                                lib=grule-rule-engine package=AST source=GRL
Final VALUE:  4000

I tried and able to execute.

janmpo commented
2024/01/04 15:11:51 GetMult Called. Returned Value:  4000
2024/01/04 15:11:51 ------------------
INFO[0000] Check C&D called                              lib=grule-rule-engine package=AST source=GRL
2024/01/04 15:11:51 ------------------
2024/01/04 15:11:51 GetMult Called. Returned Value:  12000
2024/01/04 15:11:51 ------------------
INFO[0000] Check B called                                lib=grule-rule-engine package=AST source=GRL
INFO[0000] Check A called                                lib=grule-rule-engine package=AST source=GRL
Final VALUE:  4000

I tried and able to execute.

Thanks @yjagdale. You got like me 4000 also that it is not the expected value 24000. So it seems that it was not a problem on my setup when I was doing some playground with this project and it looks that the problem is when the same external function is called with the same arguments twice.

Nevertheless, I mention on purpose @newm4n, @jinagamvasubabu and @niallnsec as they are the main contributors, in case it is a ¿bug? that they didn't realize.

Thanks and happy new year!

any update for this? I have the same issue

@janmpo @afdalwahyu Consider using the Forget built-in function

Yes, it works as you mentioned. Actually just by adding a random parameter value to the function it works also.

Thanks for your support.