Rego rule to check for employee name in a list - open-policy-agent

I am new to rego code and writing a rule to check for employee names if they present in the approved employee list. If not, it should print out the employees who are not part of the list. Here is the input I am giving:
{
"valid_employee_names": {
"first_name.2113690404": "emp_Maria",
"first_name.2641279496": "emp_Rosie",
"first_name.3921413181": "emp_Shaun",
"first_name.588579514": "emp_John"
},
"destroy": false
}
Here is the rego rule:
valid_name := {i: Reason |
check_list := {["emp_John","emp_Monika","emp_Cindy","emp_Katie","emp_Kein"]}
doc = input[i]; i="valid_employee_names"
key_ids := [k | doc[k]; startswith(k, "first_name.")]
resource := {
doc[k] : doc[replace(k, ".key", ".value")] | key_ids[_] == k
}
emp_name := [m | resource[m]; startswith(m, "emp_")]
list := {x| x:=check_list[_]}
not(list[emp_name])
Reason := sprintf("Employees not on record found - %v . :: ", emp_name)
}
I always get the same output that employees not on record are found.
Can anyone point me to see what needs to be corrected/updated?
Thanks!

The rule you posted is quite complicated. If you only need to compute the set of employee names NOT in the approved list (which I assume is input.valid_employee_names) start by computing a set of valid names and then take the difference.
# valid_employee_names generates a set of valid employee (first) names from the input.
valid_employee_names := {v |
some k
v := input.valid_employee_names[k]
startswith(k, "first_name.")}
I'm assuming the list of names to check against is check_list. Note, you have check_list defined as a set containing an array of names (represented as strings). There is no obvious need for the extra array. Just define the check_list as a set of names:
check_list := {"emp_John", "emp_Monika", "emp_Cindy", "emp_Katie", "emp_Kein"}
Now we have two sets we can easily find the invalid names using set difference:
check_list - valid_employee_names
Putting it all together:
valid_employee_names := {v |
some k
v := input.valid_employee_names[k]
startswith(k, "first_name.")}
check_list := {"emp_John", "emp_Monika", "emp_Cindy", "emp_Katie", "emp_Kein"}
invalid_names := check_list - valid_employee_names
(playground link: https://play.openpolicyagent.org/p/5KfXnSIkHa)

Related

Setting double value to NULL in Database using Delphi / FireDac

I am reading and writing data into an Microsoft Access database using Delphi 11.2 and FireDac.
I'd like to be able to set numeric values in the database to NULL based on the (double) value in delphi.
I am currently reading values as strings from the DB
myQuery.FieldByName('myNumericField').AsString
into my delphi object fields and set double values on application level to NAN if I find empty strings. When I write them back to the DB and the user has not set the value on application level the value is obviously set to NAN in the DB as well, but not to NULL, which would be preferred.
Does anybody know a feasible way to set double / numeric fields to NULL in an (microsoft Access or presumably any other) database via firedac?
Help is much appreciated!
Simplified code samples for update and insert I am currently using
update:
dbMain.ExecSQL('update MyTable set ' + 'myNumericField = :P1 ' + 'where myIDField = :P2', [myDataObject.myDoubleValue, myDataObject.myId]);
insert:
dbMain.ExecSQL
('insert into MyTable(myIDField, myNumericField) '
+ 'values(:P1, :P2)',
[myDataObject.myId, myDataObject.myDoubleValue]);
Try something like this:
var
varValue: Variant;
if IsNan(myDataObject.myDoubleValue) then
varValue := Null
else
varValue := myDataObject.myDoubleValue;
dbMain.ExecSQL('update MyTable set myNumericField = :P1 where myIDField = :P2', [varValue, myDataObject.myId]);
Alternatively:
if IsNan(myDataObject.myDoubleValue) then
dbMain.ExecSQL('update MyTable set myNumericField = NULL where myIDField = :P1', [myDataObject.myId])
else
dbMain.ExecSQL('update MyTable set myNumericField = :P1 where myIDField = :P2', [myDataObject.myDoubleValue, myDataObject.myId]);
Alternatively:
var
Params: TFDParams;
Param: TFDParam;
Params := TFDParams.Create;
try
Param := Params.Add('P1', ftFloat);
if IsNan(myDataObject.myDoubleValue) then
begin
Param.Bound := True;
Param.Clear;
end else
Param.Value := myDataObject.myDoubleValue;
Params.Add('P2', myDataObject.myId);
dbMain.ExecSQL('update MyTable set myNumericField = :P1 where myIDField = :P2', Params);
finally
Params.Free;
end;

Union an array of objects in rego

How do I union an array of objects into one object in rego?
I know how to union two objects, but how do I loop through an array of objects?
This sample works, but it is (obvious) not scalable ;)
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test4": "012"}]
b := {
"my-property": object.union(object.union(a[0], a[1]), a[2])
}
Expected output:
{
"test1": "123",
"test2": "456",
"test3": "789",
"test4": "012"
}
Thanks!
Casper
TLDR; assuming the object keys are unique (like in your example), you can simply use a comprehension:
b := {k: v |
some i, k
v := a[i][k]
}
Since the variable i is only used once, you can replace it with _ to avoid having to come up with a name:
b := {k: v |
some k
v := a[_][k]
}
If the object keys are not unique then it's slightly more complicated because you need to decide how to resolve conflicts when two objects contain the same key with different values. How you decide to resolve conflicts will depend on the use case. One way to resolve them is to just group values by their keys. For example:
# the 'test2' key appears in more than one object with a different value
a := [{"test1": "123"}, {"test2": "456"}, {"test3": "789", "test2": "012"}]
To group the values by keys, you can write a nested comprehension:
{k: vs |
# for each key 'k'
some k
conflicting[_][k]
# find all values for key 'k' and group as a set
vs := {v | v := conflicting[_][k]}
}
Output:
{
"test1": [
"123"
],
"test2": [
"012",
"456"
],
"test3": [
"789"
]
}

FireDAC Conversion not supported for query parameter on Interbase timestamp column

I'm having a problem opening a query that uses a timestamp parameter to an interbase table. After checking stackoverflow and implementing the conversion mappings for MapRules, I get a result of "Conversion is not supported" when opening the query.
Any help is deeply appreciated, Thanks!
Here is the code that sets the parameters:
with FDQuery1 do
begin
Close;
FDQuery1.FormatOptions.OwnMapRules := True;
with FormatOptions.MapRules.Add do
begin
SourceDataType := dtDateTime;
TargetDataType := dtDateTimeStamp;
end;
FDQuery1.Params.ParamByName('BalanceDate').AsDateTime := StrToDateTime('10/03/2017');
FDQuery1.ParamByName('User').AsInteger := 1;
FDQuery1.OpenOrExecute;
end;
Here is the query:
SELECT
AC.ACCOUNT_ID,
AC.ACCOUNT_NAME,
ACT.ACCOUNT_TYPE_NAME,
(Select Sum(P1.TOTAL_VAL) From PMNTDOCTRANS P1, DAYBATCH D1 Where
(P1.ACCOUNT_ID=AC.ACCOUNT_ID)
And (D1.DAY_BATCH_ID=P1.DAY_BATCH_ID)
And (D1.DAY_BATCH_DATE <= :BalanceDate)
And (D1.DAY_BATCH_STORE in (Select Id From ExtUserList Where List_Id=9 and User_Id =:User))
) As Val1,
(Select Sum(P2.TOTAL_VAL) From PMNTDOCTRANS P2, DAYBATCH D2 Where
(P2.OPOSIT_ACCOUNT=AC.ACCOUNT_ID)
And (D2.DAY_BATCH_ID=P2.DAY_BATCH_ID)
And (D2.DAY_BATCH_DATE <= :BalanceDate)
And (D2.DAY_BATCH_STORE in (Select Id From ExtUserList Where List_Id=9 and User_Id =:User))
) As Val2
FROM
ACCOUNT AC ,
ACCOUNTTYPE ACT
WHERE
(ACT.ACCOUNT_TYPE_ID = AC.ACCOUNT_TYPE_ID)
And
(AC.ACCOUNT_TYPE_ID in (Select Id From ExtUserList Where List_Id=3 and User_Id =:User))
ORDER BY ACCOUNT_TYPE_NAME
this problem solves like this
FDQuery1.Params.ParamByName('BalanceDate').Value:= StrToDateTime('10/03/2017');

How I know the index of specific value in a set

I have these codes:
TAPPUGroup = (APP_UG_USERS, APP_UG_SUPER_USERS, APP_UG_ADMINS);
TAPPUGroups = set of TAPPUGroup;
TAppUser = record
UID: integer;
UName: string;
UGroup: TAPPUGROUPS;
end;
...
LoggedUser: TAppUser;
I used include to add groups to LoggedUser.UGroup, now how I know the index of specific value in TAPPUGroup for example if APP_UG_SUPER_USERS included in LoggedUser.UGroup how I can get it's index in TAPPUGroup ?
Example: If LoggedUser.UGroup = APP_UG_SUPER_USERS then I want to return 1 if LoggedUser.UGroup = APP_UG_ADMINS I want to return 2 and so on.
If you really do want the index of a given enumeration item in the enumeration, all you need to do is just use Ord().
To go the other way, you can use the enumeration name as it it were a function:
AGroup := TAPPUGroup(1);
Anyway, Ord() is how you find the index of a given enumeration value (like APP_UG_USERS) in a contiguous enumeration declaration. To find out whether a particular set instance contains a given set element, ou use the "if xxx in ..." construct Remy shows, e.g.
if APP_UG_USERS in MySet then ...
You can also do this
var
AValue : TAPPUGroup;
MySet : TAPPUGroups ;
for AValue:= Low(TAPPUGroup) to High(TAPPUGroup) do
if AValue in MySet then ...
You don't need the index. To know if a value exists in the Set, use the in operator instead:
if APP_UG_SUPER_USERS in LoggedUser.UGroup then

What is the simplest way to work with associative strings (key/values)?

I have a lot of constants that are somehow related, at some point I need to pair them, something like this:
const
key1 = '1';
key2 = '2';
key3 = '3';
value1 = 'a';
value2 = 'b';
value3 = 'c';
I want to avoid doing:
if MyValue = key1 then Result := value1;
I know how to do it with string lists using:
MyStringList.Add(key1 + '=' + value1);
Result := MyStringList.Values[key1];
But, is there any simpler way to do this?
Yes, assignment can be done this way instead, avoiding manual string concatenation:
MyStringList.Values[Key1] := Value1;
Do a wrapper around your "value"
TMyValue = class
value: String;
end;
Then use this:
myValue := TMyValue.Create;
myValue.Value = Value1;
MyStringList.AddObject(Key1, Value1);
Then, you can sort your list, do a IndexOf(Key1) and retrieve the object.
That way, your list is sorted and the search is very fast.
You can use a multi-dimensional constant array with an enumeration for at least one of the dimensions:
Define it like this:
type
TKVEnum = (tKey, tValue); // You could give this a better name
const
Count = 3;
KeyValues: array [1..Count, TKVEnum] of string =
// This is each of your name / value paris
(('1', 'a'), ('2', 'b'), ('3', 'd'));
Then you use it like this:
if MyValue = KeyValues[1, TKVEnum.tKey] then
Result := KeyValues[1, TKVEnum.tValue]
You could use a For loop on it too. This is just as efficient as doing them as individual constant strings, but gives you the added advantage that they are intrinsically associated.
Instead of defining the first dimension numerically, I would suggest
type
TConstPairs = (tcUsername, tcDatabase, tcEtcetera);
But I guess that totally depends on what you constants represent.

Resources