How to instantiate local variables in decision tables - drools

I am pretty new to drools and am given the below task.
I am inserting a few POJO into my KieSession object and am retreiving them into variables in my decision table as follows.
CONDITION CONDITION CONDITION ACTION
abc: classABC xyz: classXYZ lmn : classLMN
var1 == $param var2 == $param
1 2 3
As far as I understand, the above table would yield the following rule
when
abc:classABC(var1==1)
xyz:classXYZ(var2==2)
lmn:classLMN(var3==3)
then
some action
What I want is to get the following.
when
abc:classABC(var1==1)
xyz:classXYZ(var2==2)
lmn:classLMN(var3==3)
fgh:classFGH($var:var4) // I think this step is creating a new variable to hold value of var4
then
some action
How do I get this on a decision table ?
I tried just adding a condition column with the variable declaration as fgh :classFGH, but since there is no data to be provided in the data row, this column would be ignored. If I do, give some data, there is an error at compile time "no code sinppet at xyz column". All I need is to declare a variable that can hold the value of the object that I have passed in my main method and use that object later in a different column of my decision table.

I'm not sure I get the requirement around the decision table, but you can 'use' the firing of a rule to create new facts and insert them, with parameters from the original events. These can then be used to trigger further rules, like so (assuming var4 is boolean):
declare AllMoonsInAlignmentEvent
#role (event)
extraCheese : boolean
end
rule "Some Rule"
when
$abc:classABC(var1==1)
$xyz:classXYZ(var2==2)
$lmn:classLMN(var3==3)
$fgh:classFGH($var:var4)
then
... some action using `$var`, `$abc` etc
AllMoonsInAlignmentEvent myEvent= new AllMoonsInAlignmentEvent();
myEvent.extraCheese = $var;
insert(myEvent);
rule "With Extra Cheese"
when
$moonsAligned:AllMoonsInAlignmentEvent(extraCheese == true)
then
...
rule "Without Extra Cheese"
when
$moonsAligned:AllMoonsInAlignmentEvent(extraCheese == false)
then
...

You can get X($y:y) into a spreadsheet in two ways. First, in column 4
X($y:y /*$param*/)
and fill the column with any character you like. The other way might be in column 3 (!)
fgh:classFGH($var:var4) lmn:classLMN
var3==$param
These tricks are always a bit iffy. Rules requiring the simple "grab" of a fact aren't typical for spreadsheets and could be the first indication that you aren't pursuing the best approach.

CONDITION
fgh:classFGH
$param:var4
Comment cell
$var

Related

LHS Make Empty List Trigger Rule

I am new to Drools so please bear over with the terminology!
Can I make rule trigger even though an empty list is involved in the LHS?
I have the code below.
There are two rules. One rule that insert facts and another rule that work on facts.
Part of fact B is to hold a list of facts A.
If this list is not empty then I am able to work on fact B.
If this list is empty then I am not able to work on fact B.
How can I write the code so I am able to work on fact B even though the list is empty.
If I activate the line below "b.As.add(a);" then I can work on fact B.
If I deactivate the line below "b.As.add(a);" then I can work not on fact B.
declare A
nameA : String
end
declare B
nameB : String
As : java.util.ArrayList
end
rule "insertfacts"
when
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
rule "checkfacts"
when
$b : B();
$a : A() from $b.As;
then
// take action based on object $b
end
Thanks
Thomas S
To check if an object is present in working memory, you should use not() or exists() (depending on what you're trying to do.)
So your insert-facts rule should be like this:
rule "insertfacts"
when
not(B())
then
A a = new A();
a.setNameA("A");
B b = new B();
b.setNameB("B");
b.As = new java.util.ArrayList();
b.As.add(a); // Only with this line rule checkfacts fires
insert( b );
end
This way you won't end up with extra B instances because insertfacts will only trigger when B doesn't exist (so, once.)
To check for an empty list, you can do size == 0 as a check:
rule "As list is empty"
when
$b: B()
ArrayList( size == 0 ) from $b.As
then
// At this point you know that As is an empty list,
// so you can insert stuff if you want
end
Alternatively, if you want to check that a specific object is not in the list, you can use not memberOf.
rule "Some instance A is not in the As list"
when
$a: A() // the A instance we're trying to check
$b: B( $a not memberOf As )
then
// at this point As is a list of any size which doesn't contain the $a instance
end
Similarly there's memberOf for checking that the specific object is in the list:
rule "Some instance A is in the As list"
when
$a: A()
$b: B( $a memberOf As )
then
// As is a list of any size which DOES contain $a
end
Note that there are two complementary operators you could be using to check that something is or is not part of a list: memberOf and contains. They're roughly the same, just with the arguments in different orders. See this other question for more information about those two operators.
Of course, if you don't care at all about the contents of the As ArrayList, then just don't check anything against it in the "when" clause. If you're sure you'll never have a situation where it's undefined, you can even skip the null check.
rule "We don't care whether there's something in As"
when
$b: B( $as: As )
then
// at this point we have access to the variable $as which will be
// the arraylist; it may be empty, it may have stuff in it
end
... but you could always do a null check if you want to be safe:
$b: B( $as: As != null)
It was mentioned in a comment that you plan to write regular code in the "then" clause and loop through the list. Usually you don't want to do that -- if you just want to do work against the members of the list or a subset of the list (eg items of the list which meet certain criteria), Drools will implement the iteration out of the box for you.
As a simple example, let's say that you want to find all of the A instances inside of the list which have the name "PURCHASE" and update the name to be "VERIFIED_PURCHASE".
rule "Update PURCHASE to VERIFIED_PURCHASE"
when
B( $as: As != null )
$a: A( name == "PURCHASE" ) from $as
then
$a.name = "VERIFIED_PURCHASE"
end
(You could add an update call on the right hand side if it's important to reevaluate rule matches, but in this simple example it's not needed.)
What will happen here, and what's usually a little difficult for people new to Drools to wrap their heads around, is that Drools will internally iterate through the As array list and check each item to see if it matches the requested criteria (name == "PURCHASE"). For each item it finds that matches the criteria, it will trigger the right hand side.
So for this example, if As has 10 items and 3 are named "PURCHASE", this rule's consequences ("then" clause) will trigger 3 times, once per matched item.
If you need a collection of these matches on the right hand side, you can use collect or accumulate depending on how complex your use case is.
rule "Get all purchases"
when
B( $as: As != null )
$purchases: ArrayList() from collect( A( name == "PURCHASE" ) from $as )
then
// do something with $purchases here
end
Of course, you shouldn't then turn around and iterate through $purchases on the right hand side -- use the built-in way I showed previously.
The Drools engine is extremely good at optimizing performance; anything you put in the "when" clause gets optimized by Drools, which is why you want to do the iteration there (and let Drools leverage its own internal capabilities at that.)
The right hand side ("then" clause) is not optimized, so by putting the iteration and other logic on that side is going to make your rules perform worse in comparison. You may not notice the difference in a toy example like the ones we're working on in this question, but once you start processing hundreds or thousands of requests per minute, it will definitely start showing.
(I spent 10 years of my career supporting an embarrassingly huge collection of rules (scale is millions of rules) and I've never had the need to actually write a for-loop or any sort of iteration in the "then" clause. And the few places where I did find them as added by other engineers, I was able to remove them for non-negligible performance improvements. Let the framework do what it's good at.)

newbie - Drools filter from same fact

I am newbie to drools. Need understanding how to cascade filtered data from one condition to next one as shown below.
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething()
$var3.doVar3Stuff()
end
How can I achieve that?
Continuation of my original post..
i have below rule computing statistics of a rule execution as below.
rule "myrule"
when
$a:wrapper(isActive("ruleName") && dataTypeCode.equals("typeCodeinDB"))
$total : Number() from accumulate($entity: MyObj(ObjAttribute=='testValue') from $a.getMyObj() , count( $entity ))
$filter: MyObj(ObjAttribute=="testValue" && ObjAttribute2.equals("ValidValue")) from $a.getMyObj()
then
$filter.addToResults($filter.getRuleConstituents(),1,drools.getRule().getName(),null); // Add data for insertion in DB at later stage
$filter.addPayloadExecution($a.dataTypeCode(),$a.getMyObj().toString(),$a.getMyObj().size()); //Total Count of MyObj
$filter.addRuleExecutions(drools.getRule().getName(), $total); // anotehr slice of data
end
Here.. i am computing two stats.. Total count of MYObj(), apply some filter for data validation and compute count of valid list ($filter is valid data)
When I run that code I am getting error as below
Exception executing consequence for rule "MyRule" in moneta.rules: [Error: $filter.addRuleExecutions(drools.getRule().getName(), $total): null]
[Near : {... $filter.addToResults($filter.get ....}]
^
[Line: 1, Column: 1]
Need advice on resolving this.
I can't tell if you're trying to change the data in one rule and make it visible in another, or if you're trying to do a single complex condition in a single rule. So I'll outline how both work.
Complex conditions
The pseudo-code in your rule indicates that you have a main object with a filter and a collection of sub-objects. You need to get a list of all sub-objects without a filter, and also to get a list of sub-objects that meet some sort of condition.
To collect these sublists, we have two available operations: accumulate and collect. Since you didn't give a real example, just some psuedo-code, I can't say for sure which is the correct one for you to use. The example I present below uses collect because it is appropriate for this use case. For more information about these operations, refer to the Drools documentation. I've linked to the section on operators; scroll down to see the details for collect and accumulate.
So imagine I have an app that is modeling a school, and my KindergartenClass object has a List of Student objects. In this case, KindergartenClass is the main object, and Student is the sub-object. My Students have names, ages, and an Allergies object that indicates food allergies. Students without Allergies have no allergy object. This is going to be the filter for the example rule -- finding the students without allergies.
The rule would then look like:
rule "EXAMPLE"
when
KindergartenClass( $students: students ) // $var1
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find all students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $students )
then
...
end
Obviously your right-hand-side would have to be adjusted because 'List' has no doSomething() and doVar3Stuff() methods. But without an actual example rule for what you're attempting I can't be more specific here.
You could get the individual students from the lists like this:
$studentWithoutAllergies: Student() from $var2
$studentOlderThan5: Student() from $var3
... but then your then-clause would trigger once for every single $studentWithoutAllergies and $studentOlderThan5. Most people don't want a cartesian product all elements in two lists and then having their consequences firing for each of those products.
Of course, you also talk about "cascading", though it's rather unclear what you mean. If you mean that you want $var3 to be a subset of what you found in $var2, then it's as simple as changing the from clause. So in the example I've been working with, if you actually want all students older than 5 who have no allergies, you can simply change the when clause to be:
// Find all students with no allergies, $var2
$var2: List() from collect( Student( allergies == null ) from $students )
// Find the subset of the non-allergy students who are older than 5, $var3
$var3: List() from collect( Student( age > 5 ) from $var2 )
Changing the data that triggers the rule
There are two keywords available for changing the data available to the left hand side (when clause): update and insert. Which one you use depends on what you're trying to do, and come with very different considerations in terms of performance.
When you ask Drools to fire a set of rules, it first goes through all of the rules and determines if the rule fires, given the data you've inputted. Basically it just goes through, in order, all of the when clauses and decides if the rule is activated or not. Then, once it has the sub-set of rules, in order, it goes through, one by one, executing the right hand side.
If you call update in your right hand side (then clause), then this process repeats: all rules are evaluated again, from the top, including the rule you just fired. If you call insert in your then clause, then the new data is put into working memory, and Drools re-evaluates all of the remaining rules in its execution list. (So, for example, if Drools had decided that rules A, B, C, and D met their when criteria, and B inserts a new fact into working memory, then C and D would be re-evaluated to make sure that they were still valid.)
This is how you'd use them:
rule "Example rule with Update"
when
$input: MyAwesomeInputObject(someValue == 0)
then
$input.setSomeValue(99);
$input.doSomeStuff();
update($input);
end
rule "Example rule with Insert"
when
Person(name == "Bob")
not( Age() )
then
Age age = new Age(18);
insert(age); // there is now an Age object in working memory
end
I'm not 100% familiar with your use case, but I'm going to assume you want to fire all of the filtering rules a second time after your initial rule fires, with the updated data. So I'd do something like this:
rule "rulename"
when
$var1 : MainObj(filter1)
$var2 : SubObj() from $var1.getSubObj // need all SubObj without any filter here for then clause.
$var3 : SubObj(conditions) from $var2 // Need a subset of SubObj for another then clause
then
$var2.doSomething();
$var3.doVar3Stuff();
update($var1); // update the var1 in working memory
end
I have no idea what your when clause is trying to do, since it doesn't appear to match the comments and the syntax is completely wonky. So only the right hand side (then clause) has been adjusted. I have added update($var1) so it will refire the rules with the new instance of $var1 that should have your changes in it.
Of course, once you start re-firing all rules, you run the risk of having rules loop. If this does happen, you will see spikes of CPU and apparent thread deadlocks (this is useful to alert on.) Note that there is a no-loop property, but it won't help when you're calling update.

Drools - How to check if a fact exists in a decision table

I am using Drools 5.5.0, and I have a decision table, demonstrated below:
When I run the rules engine, I only ever insert one SecurityContext and once JSONWrapper at a time.
Based on this table alone, none of my rules ever get fired (however they all get evaluated). I believe this is because for the second condition, the cells are blank for each role/rule.
This is what I am trying to go for, in DRL:
package com.acme.security.rules.widget;
import com.acme.test.RuleTableTest.SecurityContext;
import com.acme.test.RuleTableTest.JSONWrapper;
rule "widget_accessibility_store_manager"
when
$sc : SecurityContext()
$output : JSONWrapper()
eval($sc.hasRole("Store Manager"))
then
$output.setFeatureVisibility("feature1", "yes");
$output.setFeatureVisibility("feature2", "yes");
$output.setFeatureVisibility("feature3", "yes");
$output.setFeatureVisibility("feature4", "yes");
$output.setFeatureVisibility("feature5", "yes");
end
This rule fires just fine.
How do I have a condition in my decision table that just checks for the presence of an object, without any other constraints? (Just like in my DRL) I need this object so I can use it as an output in the action statements. I also am trying to leave the cells for each rule in the column blank for simplicity.
It would be more convenient to create the "output object" on the right and side, and you can insert it or pass it to a global collection.
The somewhat contrived workaround for including a condition for the mere presence of a fact looks like this:
CONDITION
$output : JSONWrapper
/*$param*/
mark below to force inclusion
x
Note that you can join cells vertically.
I know it's too late for original poster at this point, but confronted with a similar problem my solution was to look for a value in that object that for sure isn't going to be null. For example:
rule "widget_accessibility_store_manager"
when
$sc : SecurityContext(role != null)
$output : JSONWrapper(featureVisibility!=null)
eval($sc.hasRole("Store Manager"))
then
$output.setFeatureVisibility("feature1", "yes");
$output.setFeatureVisibility("feature2", "yes");
$output.setFeatureVisibility("feature3", "yes");
$output.setFeatureVisibility("feature4", "yes");
$output.setFeatureVisibility("feature5", "yes");
end
And then your excel file would look kind of like this:
If you don't have mandatory parameters on these objects, you may even be able to skip the inner parameter and check if SecurityContext and itself is null. Alternatively you could check for "exists SecurityContext".

Assigning a whole DataStructure its nullind array

Some context before the question.
Imagine file FileA having around 50 fields of different types. Instead of all programs using the file, I tried having a service program, so the file could only be accessed by that service program. The programs calling the service would then receive a DataStructure based on the file structure, as an ExtName. I use SQL to recover the information, so, basically, the procedure would go like this :
Datastructure shared by service program :
D FileADS E DS ExtName(FileA) Qualified
Procedure called by programs :
P getFileADS B Export
D PI N
D PI_IDKey 9B 0 Const
D PO_DS LikeDS(FileADS)
D LocalDS E DS ExtName(FileA) Qualified
D NullInd S 5i 0 Array(50) <-- Since 50 fields in fileA
//Code
Clear LocalDS;
Clear PO_DS;
exec sql
SELECT *
INTO :LocalDS :nullind
FROM FileA
WHERE FileA.ID = :PI_IDKey;
If SqlCod <> 0;
Return *Off;
EndIf;
PO_DS = LocalDS;
Return *On;
P getFileADS E
So, that procedure will return a datastructure filled with a record from FileA if it finds it.
Now my question : Is there any way I can assign the %nullind(field) = *On without specifying EACH 50 fields of my file?
Something like a loop
i = 1;
DoW (i <= 50);
if nullind(i) = -1;
%nullind(datastructure.field) = *On;
endif;
i++;
EndDo;
Cause let's face it, it'd be a pain to look each fields of each file every time.
I know a simple chain(n) could do the trick
chain(n) PI_IDKey FileA FileADS;
but I really was looking to do it with SQL.
Thank you for your advices!
OS Version : 7.1
First, you'll be better off in the long run by eliminating SELECT * and supplying a SELECT list of the 50 field names.
Next, consider these two web pages -- Meaningful Names for Null Indicators and Embedded SQL and null indicators. The first shows an example of assigning names to each null indicator to match the associated field names. It's just a matter of declaring a based DS with names, based on the address of your null indicator array. The second points out how a null indicator array can be larger than needed, so future database changes won't affect results. (Bear in mind that the page shows a null array of 1000 elements, and the memory is actually relatively tiny even at that size. You can declare it smaller if you think it's necessary for some reason.)
You're creating a proc that you'll only write once. It's not worth saving the effort of listing the 50 fields. Maybe if you had many programs using this proc and you had to create the list each time it'd be a slight help to use SELECT *, but even then it's not a great idea.
A matching template DS for the 50 data fields can be defined in the /COPY member that will hold the proc prototype. The template DS will be available in any program that brings the proc prototype in. Any program that needs to call the proc can simply specify LIKEDS referencing the template to define its version in memory. The template DS should probably include the QUALIFIED keyword, and programs would then use their own DS names as the qualifying prefix. The null indicator array can be handled similarly.
However, it's not completely clear what your actual question is. You show an example loop and ask if it'll work, but you don't say if you had a problem with it. It's an array, so a loop can be used much like you show. But it depends on what you're actually trying to accomplish with it.
for old school rpg just include the nulls in the data structure populated with the select statement.
select col1, ifnull(col1), col2, ifnull(col2), etc. into :dsfilewithnull where f.id = :id;
for old school rpg that can't handle nulls remove them with the select statement.
select coalesce(col1,0), coalesce(col2,' '), coalesce(col3, :lowdate) into :dsfile where f.id = :id;
The second method would be easier to use in a legacy environment.
pass the key by value to the procedure so you can use it like a built in function.
One answer to your question would be to make the array part of a data structure, and assign *all'0' to the data structure.
dcl-ds nullIndDs;
nullInd Ind Dim(50);
end-ds;
nullIndDs = *all'0';
The answer by jmarkmurphy is an example of assigning all zeros to an array of indicators. For the example that you show in your question, you can do it this way:
D NullInd S 5i 0 dim(50)
/free
NullInd(*) = 1 ;
Nullind(*) = 0 ;
*inlr = *on ;
return ;
/end-free
That's a complete program that you can compile and test. Run it in debug and stop at the first statement. Display NullInd to see the initial value of its elements. Step through the first statement and display it again to see how the elements changed. Step through the next statement to see how things changed again.
As for "how to do it in SQL", that part doesn't make sense. SQL sets the values automatically when you FETCH a row. Other than that, the array is used by the host language (RPG in this case) to communicate values back to SQL. When a SQL statement runs, it again automatically uses whatever values were set. So, it either is used automatically by SQL for input or output, or is set by your host language statements. There is nothing useful that you can do 'in SQL' with that array.

Dynamically reference temp-table column values in Progress

I am using Progress 4GL
I have a spreadsheet of data containing several columns called data1....50.
I have created a temp table which holds all the values.
Now I would like to loop through the temp table columns and do various calculations
So I need something like this:
for each record loop thru cols_in_temp_table .
if col_value = "XYZ" then
do calcs and stuff
end.
So how do I reference the temp_table cols ?
Ok, didn't resolve the original query, but found a workaround. Split the data up and put into separate tables, long winded, but does the trick.
Depending on your version, this is one way to do it:
DEFINE VARIABLE h-cols AS HANDLE NO-UNDO.
h-cols = tt-cols:BUFFER-HANDLE.
FOR EACH tt-cols
NO-LOCK:
IF h-cols::col-name = "some value" THEN
RUN do-something.
END.
For versions that can't do the "::" operator, do this:
FOR EACH tt-cols
NO-LOCK:
IF h-cols::buffer-field("col-name"):buffer-value = "some value" THEN
RUN do-something.
END.