hyperjumptech / grule-rule-engine

Rule engine implementation in Golang
Other
2.16k stars 338 forks source link

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

Closed janmpo closed 3 months ago

janmpo commented 9 months ago

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)

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

}


2. 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

4. Real value should be in all the cases 24000
5. 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 9 months ago

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 9 months ago

Hi,

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

Thanks

janmpo

yjagdale commented 8 months ago
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 8 months ago
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!

afdalwahyu commented 7 months ago

any update for this? I have the same issue

thanhfphan commented 3 months ago

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

janmpo commented 3 months ago

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

Thanks for your support.