From 0b9ca45cc5801a68ad7e0145f10a6c07df8ccaf4 Mon Sep 17 00:00:00 2001 From: Senthil Nathan Date: Mon, 7 Jun 2021 14:03:52 -0400 Subject: [PATCH] Changes done for v1.1.0. --- CHANGELOG.md | 4 + .../EvalPredicateExample.spl | 40 ++++- .../FunctionalTests.spl | 70 +++++++- impl/include/eval_predicate.h | 161 ++++++++++++++---- info.xml | 2 +- 5 files changed, 234 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3c4aca..c6c2865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changes +## v1.1.0 +* June/07/2021 +* Modified it to support the in operational verb for int32, float64 and rstring based tuple attributes and the inCI operational verb only for rstring based tuple attributes. + ## v1.0.9 * June/01/2021 * Added two new operational verbs: in and inCI. diff --git a/com.ibm.streamsx.eval_predicate/EvalPredicateExample.spl b/com.ibm.streamsx.eval_predicate/EvalPredicateExample.spl index 288d977..630ff25 100644 --- a/com.ibm.streamsx.eval_predicate/EvalPredicateExample.spl +++ b/com.ibm.streamsx.eval_predicate/EvalPredicateExample.spl @@ -8,7 +8,7 @@ /* ================================================================== First created on: Mar/05/2021 -Last modified on: June/01/2021 +Last modified on: June/07/2021 This is an example application that shows how to use the eval_predicate function to evaluate an SPL expression a.k.a @@ -859,13 +859,15 @@ composite EvalPredicateExample { } // ============== TESTCASE GROUP 5 ============== - type Role_t = rstring role, int32 x, float64 y; + type Role_t = rstring role, int32 age, float64 salary; mutable Role_t myRole = {}; myRole.role = "Admin"; + myRole.age = 56; + myRole.salary = 10514.00; // // 5.1 - // Usage of in + // Usage of in for an rstring tuple attribute // It does case sensitive membership test using a list literal string. rule = 'role in ["Developer", "Tester", "Admin", "Manager"]'; result = eval_predicate(rule, myRole, error, $EVAL_PREDICATE_TRACING); @@ -880,7 +882,7 @@ composite EvalPredicateExample { // // 5.2 - // Usage of inCI + // Usage of inCI for an rstring tuple attribute // It does case insensitive membership test using a list literal string. myRole.role = "mAnAgEr"; rule = 'role inCI ["Developer", "Tester", "Admin", "Manager"]'; @@ -893,6 +895,36 @@ composite EvalPredicateExample { } else { printStringLn("Testcase 5.2: Evaluation execution failed. Error=" + (rstring)error); } + + // + // 5.3 + // Usage of in for an int32 tuple attribute + // It does membership test using a list literal string. + rule = 'age in [22, 40, 43, 50, 56, 65]'; + result = eval_predicate(rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase 5.3: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase 5.3: Evaluation criteria is not met."); + } else { + printStringLn("Testcase 5.3: Evaluation execution failed. Error=" + (rstring)error); + } + + // + // 5.4 + // Usage of in for a float64 tuple attribute + // It does membership test using a list literal string. + rule = 'salary in [68326.45, 92821.87, 10514.00, 120529,32]'; + result = eval_predicate(rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase 5.4: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase 5.4: Evaluation criteria is not met."); + } else { + printStringLn("Testcase 5.4: Evaluation execution failed. Error=" + (rstring)error); + } } // End of onTuple S } // End of the Custom operator. } // End of the main composite. diff --git a/com.ibm.streamsx.eval_predicate/FunctionalTests.spl b/com.ibm.streamsx.eval_predicate/FunctionalTests.spl index 51225e6..8b4e3d8 100644 --- a/com.ibm.streamsx.eval_predicate/FunctionalTests.spl +++ b/com.ibm.streamsx.eval_predicate/FunctionalTests.spl @@ -8,7 +8,7 @@ /* ================================================================== First created on: Mar/28/2021 -Last modified on: June/01/2021 +Last modified on: June/07/2021 This application is meant for doing several hundred functional tests to provide as much coverage as possible to @@ -6768,11 +6768,13 @@ composite FunctionalTests { } // ------------------------- - // A54.1 (Use of in for membership test.) + // A54.1 (Use of in for rstring membership test.) // It does case sensitive membership test using a list literal string. - type Role_t = rstring role, int32 x, float64 y; + type Role_t = rstring role, int32 age, float64 salary; mutable Role_t myRole = {}; myRole.role = "Admin"; + myRole.age = 56; + myRole.salary = 10514.00; _rule = 'role in ["Developer", "Tester", "Admin", "Manager"]'; result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); @@ -6785,11 +6787,10 @@ composite FunctionalTests { printStringLn("Testcase A54.1: Evaluation execution failed. Error=" + (rstring)error); } - // A54.2 (Use of inCI for membership test.) + // A54.2 (Use of inCI for rstring membership test.) // It does case insensitive membership test using a list literal string. myRole.role = "mAnAgEr"; _rule = 'role inCI ["Developer", "Tester", "Admin", "Manager"]'; - result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); if(result == true) { @@ -6799,6 +6800,58 @@ composite FunctionalTests { } else { printStringLn("Testcase A54.2: Evaluation execution failed. Error=" + (rstring)error); } + + // A54.3 (Use of in for int32 membership test.) + // It does membership test using a list literal string. + _rule = 'age in [22, 40, 43, 50, 56, 65]'; + result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase A54.3: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase A54.3: Evaluation criteria is not met."); + } else { + printStringLn("Testcase A54.3: Evaluation execution failed. Error=" + (rstring)error); + } + + // A54.4 (Use of in for float64 membership test.) + // It does membership test using a list literal string. + _rule = 'salary in [68326.45, 92821.87, 10514.00, 120529,32]'; + result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase A54.4: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase A54.4: Evaluation criteria is not met."); + } else { + printStringLn("Testcase A54.4: Evaluation execution failed. Error=" + (rstring)error); + } + + // A54.5 (Use of in for int32 membership test.) + // It does membership test using a list literal string. + _rule = 'age in [22, 40, 43, 50, 58, 65]'; + result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase A54.5: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase A54.5: Evaluation criteria is not met."); + } else { + printStringLn("Testcase A54.5: Evaluation execution failed. Error=" + (rstring)error); + } + + // A54.6 (Use of in for float64 membership test.) + // It does membership test using a list literal string. + _rule = 'salary in [68326.45, 92821.87, 10594.76, 120529,32]'; + result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); + + if(result == true) { + printStringLn("Testcase A54.6: Evaluation criteria is met."); + } else if(result == false && error == 0) { + printStringLn("Testcase A54.6: Evaluation criteria is not met."); + } else { + printStringLn("Testcase A54.6: Evaluation execution failed. Error=" + (rstring)error); + } // ------------------------- } // End of onTuple MTD. } // End of the HappyPathSink operator. @@ -8108,10 +8161,11 @@ composite FunctionalTests { // ------------------------- // B82.1 (INCOMPATIBLE_IN_OPERATION_FOR_LHS_ATTRIB_TYPE 146) - type Role2_t = rstring role, int32 x, float64 y; + type Role2_t = rstring role, int32 x, float32 y; mutable Role2_t myRole = {}; myRole.role = "Tester"; - _rule = 'x in ["Developer", "Tester", "Admin", "Manager"]'; + + _rule = 'y in [1.1, 2.2, 3.3, 4.4, 5.5]'; result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); if(result == true) { @@ -8123,7 +8177,7 @@ composite FunctionalTests { } // B82.2 (INCOMPATIBLE_IN_CI_OPERATION_FOR_LHS_ATTRIB_TYPE 147) - _rule = 'x inCI ["Developer", "Tester", "Admin", "Manager"]'; + _rule = 'x inCI [1, 2, 3, 4, 5]'; result = eval_predicate(_rule, myRole, error, $EVAL_PREDICATE_TRACING); if(result == true) { diff --git a/impl/include/eval_predicate.h b/impl/include/eval_predicate.h index fd86bf0..b9997d5 100644 --- a/impl/include/eval_predicate.h +++ b/impl/include/eval_predicate.h @@ -7,7 +7,7 @@ /* ============================================================ First created on: Mar/05/2021 -Last modified on: June/01/2021 +Last modified on: June/07/2021 This toolkit's public GitHub URL: https://github.com/IBMStreams/streamsx.eval_predicate @@ -3881,18 +3881,14 @@ namespace eval_predicate_functions { // We will allow substring related operation verbs only for // string based attributes in a non-set data type. - // We will support in and inCI for string based LHS. - // e-g: 'role in ["Developer", "Tester", "Admin", "Manager"]' if(currentOperationVerb == "startsWith" || currentOperationVerb == "endsWith" || currentOperationVerb == "notStartsWith" || currentOperationVerb == "notEndsWith" || - currentOperationVerb == "in" || currentOperationVerb == "startsWithCI" || currentOperationVerb == "endsWithCI" || currentOperationVerb == "notStartsWithCI" || - currentOperationVerb == "notEndsWithCI" || - currentOperationVerb == "inCI") { + currentOperationVerb == "notEndsWithCI") { if(lhsAttribType != "rstring" && lhsAttribType != "list" && lhsAttribType != "map" && @@ -3909,18 +3905,14 @@ namespace eval_predicate_functions { error = INCOMPATIBLE_NOT_STARTS_WITH_OPERATION_FOR_LHS_ATTRIB_TYPE; } else if(currentOperationVerb == "notEndsWith") { error = INCOMPATIBLE_NOT_ENDS_WITH_OPERATION_FOR_LHS_ATTRIB_TYPE; - } else if(currentOperationVerb == "in") { - error = INCOMPATIBLE_IN_OPERATION_FOR_LHS_ATTRIB_TYPE; } else if(currentOperationVerb == "startsWithCI") { error = INCOMPATIBLE_STARTS_WITH_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; } else if(currentOperationVerb == "endsWithCI") { error = INCOMPATIBLE_ENDS_WITH_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; } else if(currentOperationVerb == "notStartsWithCI") { error = INCOMPATIBLE_NOT_STARTS_WITH_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; - } else if(currentOperationVerb == "notEndsWithCI") { - error = INCOMPATIBLE_NOT_ENDS_WITH_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; } else { - error = INCOMPATIBLE_IN_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; + error = INCOMPATIBLE_NOT_ENDS_WITH_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; } return(false); @@ -3938,6 +3930,67 @@ namespace eval_predicate_functions { } } // End of validating string based starts, ends operator verbs. + // We will allow membership evaluation in a list string literal via + // in and inCI operation verbs. We will support this for + // any LHS tuple attribute that is of type int32, float64 and rstring. + // e-g: [1, 2, 3, 4] + // [1.4, 5.3, 98.65, 7.2] + // ["Developer", "Tester", "Admin", "Manager"] + if(currentOperationVerb == "in") { + // We will allow 'in' verb for int32, float64 and rstring LHS attributes. + if(lhsAttribType != "rstring" && + lhsAttribType != "list" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "int32" && + lhsAttribType != "float64") { + // This operator verb is not allowed for a given LHS attribute type. + error = INCOMPATIBLE_IN_OPERATION_FOR_LHS_ATTRIB_TYPE; + return(false); + } else { + // We have a compatible operation verb for a given LHS attribute type. + // Move the idx past the current operation verb. + idx += Functions::String::length(currentOperationVerb); + + // Special operation verbs such as in must be + // followed by a space character. + if(idx < stringLength && myBlob[idx] != ' ') { + error = SPACE_NOT_FOUND_AFTER_SPECIAL_OPERATION_VERB; + return(false); + } + } + } // End of validating in operator verb. + + if(currentOperationVerb == "inCI") { + // We will allow 'inCI' verb only for rstring. + // Because, case insensitivity makes sense only for rstring. + if(lhsAttribType != "rstring" && + lhsAttribType != "list" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map" && + lhsAttribType != "map") { + // This operator verb is not allowed for a given LHS attribute type. + error = INCOMPATIBLE_IN_CI_OPERATION_FOR_LHS_ATTRIB_TYPE; + return(false); + } else { + // We have a compatible operation verb for a given LHS attribute type. + // Move the idx past the current operation verb. + idx += Functions::String::length(currentOperationVerb); + + // Special operation verbs such as inCI must be + // followed by a space character. + if(idx < stringLength && myBlob[idx] != ' ') { + error = SPACE_NOT_FOUND_AFTER_SPECIAL_OPERATION_VERB; + return(false); + } + } + } // End of validating inCI operator verb. + // Store the current operation verb in the subexpression layout list. Functions::Collections::appendM(subexpressionLayoutList, currentOperationVerb); @@ -4050,6 +4103,7 @@ namespace eval_predicate_functions { currentOperationVerb != "notContains" && currentOperationVerb != "containsCI" && currentOperationVerb != "notContainsCI" && + currentOperationVerb != "in" && Functions::String::findFirst(currentOperationVerb, "size") != 0) && (lhsAttribType == "int32" || lhsAttribType == "uint32" || lhsAttribType == "int64" || @@ -4184,6 +4238,7 @@ namespace eval_predicate_functions { currentOperationVerb != "notContains" && currentOperationVerb != "containsCI" && currentOperationVerb != "notContainsCI" && + currentOperationVerb != "in" && Functions::String::findFirst(currentOperationVerb, "size") != 0) && (lhsAttribType == "float32" || lhsAttribType == "float64" || @@ -4394,13 +4449,15 @@ namespace eval_predicate_functions { } // End of if(lhsAttribType == "rstring" ... // If the operation verb is in or inCI, then the RHS - // must follow this format. - // e-g: 'role in ["Developer", "Tester", "Admin", "Manager"]' + // must follow a list string literal format. + // e-g: [1, 2, 3, 4] + // [1.4, 5.3, 98.65, 7.2] + // ["Developer", "Tester", "Admin", "Manager"] if(currentOperationVerb == "in" || currentOperationVerb == "inCI") { // The necessary check to ensure that the in or inCI - // operation verb is only associated with a - // string based LHS was already done in the LHS validation stage above. + // operation verb is only associated with a int32, float64 and + // rstring based LHS was already done in the LHS validation stage above. // // In this case, RHS must start with [ and end with ]. // RHS can have any character from space to ~ i.e. character 20 t0 126. @@ -5402,6 +5459,48 @@ namespace eval_predicate_functions { rstring const & lhsValue = myMap.at(listIndexOrMapKeyValue); performRStringEvalOperations(lhsValue, rhsValue, operationVerb, subexpressionEvalResult, error); + // ****** in membership evaluation for int32 LHS attributes ****** + } else if(operationVerb == "in" && lhsAttributeType == "int32") { + int32 const & myLhsValue = cvh; + + try { + // We can use spl_cast to convert a list string literal into + // an SPL list. As long as the string representation truly + // reflects an SPL list syntax, this conversion will work. + // If not, it will throw an exception. + // I learned about this technique by looking at the + // auto generated C++ code for a similar conversion done in + // a SPL application logic. + const SPL::list tokens = + SPL::spl_cast, SPL::rstring>::cast(rhsValue); + subexpressionEvalResult = + Functions::Collections::has(tokens, myLhsValue); + } catch(...) { + // SPL type casting of a list string literal to SPL list failed. + subexpressionEvalResult = false; + error = INVALID_RHS_LIST_LITERAL_STRING_FOUND_FOR_IN_OR_IN_CI_OPVERB; + } + // ****** in membership evaluation for float64 LHS attributes ****** + } else if(operationVerb == "in" && lhsAttributeType == "float64") { + float64 const & myLhsValue = cvh; + + try { + // We can use spl_cast to convert a list string literal into + // an SPL list. As long as the string representation truly + // reflects an SPL list syntax, this conversion will work. + // If not, it will throw an exception. + // I learned about this technique by looking at the + // auto generated C++ code for a similar conversion done in + // a SPL application logic. + const SPL::list tokens = + SPL::spl_cast, SPL::rstring>::cast(rhsValue); + subexpressionEvalResult = + Functions::Collections::has(tokens, myLhsValue); + } catch(...) { + // SPL type casting of a list string literal to SPL list failed. + subexpressionEvalResult = false; + error = INVALID_RHS_LIST_LITERAL_STRING_FOUND_FOR_IN_OR_IN_CI_OPVERB; + } // ****** Collection size check (OR) Collection item existence evaluations ****** } else if(lhsAttributeType == "set") { SPL::set const & mySet = cvh; @@ -7561,19 +7660,22 @@ namespace eval_predicate_functions { } } else if(operationVerb == "in" || operationVerb == "inCI") { // In this case, RHS value is a list string literal. - // e-g: ["Developer", "Tester", "Admin", "Manager"] + // e-g: [1, 2, 3, 4] + // [1.4, 5.3, 98.65, 7.2] + // ["Developer", "Tester", "Admin", "Manager"] + // // We must convert this to a SPL list and then // do a membership test. try { // We can use spl_cast to convert a list string literal into // an SPL list. As long as the string representation truly - // reflacts an SPL list syntax, this conversion will work. + // reflects an SPL list syntax, this conversion will work. // If not, it will throw an exception. // I learned about this technique by looking at the // auto generated C++ code for a similar conversion done in // a SPL application logic. - const SPL::list tokens = - SPL::spl_cast, SPL::rstring >::cast(rhsValue); + const SPL::list tokens = + SPL::spl_cast, SPL::rstring>::cast(rhsValue); if(operationVerb == "in") { // This is a case sensitive list membership test. @@ -10403,20 +10505,19 @@ namespace eval_predicate_functions { =============================================================================== Coda ---- -Whatever lies ahead for IBM Streams beyond 2Q2021, I'm proud to have +Whatever path lies ahead for IBM Streams beyond 2Q2021, I'm proud to have played a key role in this product team from 2007 to 2021. IBM Streams gave me marvelous opportunities to create beautiful extensions, build cutting edge streaming analytics solutions, coach/train top notch customers -and co-create meaningful production-ready software assets with them. Thus far, -it formed the best period in my 36 years of Software career. I'm lucky to -get a chance to build this challenging Rule Processing toolkit for meeting a -critical business need of a prestigious customer in the semiconductor industry. -Most likely, it is the last hurrah in my technical contributions to the -incomparable IBM Streams. I will be thrilled to get another chance to -associate with this wonderful product if it finds a new home either inside or -outside of IBM. Until then, I will continue to reminisce this unforgettable -journey made possible by the passionate researchers, engineers and managers -who are all simply world class much like IBM Streams. +and co-create meaningful production-grade software assets with them. Thus far, +it formed the best period in my 36 years of Software career. I created +this challenging Rule Processing toolkit for meeting a critical business need +of a prestigious customer in the semiconductor industry. Most likely, it is the +last hurrah in my technical contributions to the incomparable IBM Streams. +I will be thrilled to get another chance to associate with this wonderful +product if it finds a new home either inside or outside of IBM. Until then, +I will continue to reminisce this unforgettable journey made possible by the +passionate researchers, engineers and managers who are all simply world class. It will take many more years for some other company or an open-source group to roll out a full-featured product that can be a true match for IBM Streams. diff --git a/info.xml b/info.xml index 57accda..42d6f25 100644 --- a/info.xml +++ b/info.xml @@ -4,7 +4,7 @@ eval_predicate Toolkit for user defined rule (expression) processing - 1.0.9 + 1.1.0 4.2.1.6