From cd448cd54ae81605bfd072f56d89f64cfd8fe867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Wed, 31 Jul 2024 10:49:43 +0200 Subject: [PATCH 01/27] [NAE-1997] Query language - add basic antlr grammar, maven dependency and plugin --- pom.xml | 24 +++++++ src/main/resources/antlr4/QueryLang.g4 | 97 ++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 src/main/resources/antlr4/QueryLang.g4 diff --git a/pom.xml b/pom.xml index 31213b1ecce..3af8eae78d8 100644 --- a/pom.xml +++ b/pom.xml @@ -505,6 +505,14 @@ jackson-module-jsonSchema 2.13.2 + + + + org.antlr + antlr4-runtime + 4.13.1 + + @@ -779,6 +787,22 @@ license-maven-plugin 2.0.0 + + org.antlr + antlr4-maven-plugin + 4.7.1 + + src/main/resources/antlr4 + ${project.build.directory}/generated-sources/java/com/netgrif/application/engine/antlr4 + + + + + antlr4 + + + + diff --git a/src/main/resources/antlr4/QueryLang.g4 b/src/main/resources/antlr4/QueryLang.g4 new file mode 100644 index 00000000000..65bb372d8e8 --- /dev/null +++ b/src/main/resources/antlr4/QueryLang.g4 @@ -0,0 +1,97 @@ +grammar QueryLang; + +query: resource delimeter conditions EOF ; + +resource: CASE + | TASK + | USER + | PROCESS ; + +// resource types +CASE: C A S E | C A S E S ; +TASK: T A S K | T A S K S ; +USER: U S E R | U S E R S ; +PROCESS: P R O C E S S | P R O C E S S E S ; + +// delimeter +delimeter: WHERE_DELIMETER | COLON_DELIMETER ; +WHERE_DELIMETER: SPACE W H E R E SPACE ; +COLON_DELIMETER: SPACE? ':' SPACE ; + +conditions: orExpression ; + +orExpression: andExpression (SPACE OR SPACE andExpression)*; + +andExpression: conditionGroup (SPACE AND SPACE conditionGroup)*; + +conditionGroup: condition | '(' SPACE? orExpression SPACE? ')' SPACE? ; + +condition: (NOT SPACE)? attribute SPACE operator SPACE value SPACE?; + +AND: A N D | '&' ; +OR: O R | '|' ; +NOT: N O T | '!' ; + +attribute: ID | TITLE; + +ID: I D ; +TITLE: T I T L E ; + +operator: EQ + | LT + | GT + | LTE + | GTE + | CONTAINS + ; + +// operators +EQ: E Q | '==' ; +LT: L T | '<' ; +GT: G T | '>' ; +LTE: L T E | '<=' ; +GTE: G T E | '>=' ; +CONTAINS: C O N T A I N S | '~'; + +value: STRING + | NUMBER + | DATE + ; + +// basic types +STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; +NUMBER: DIGIT+ ('.' DIGIT+)? ; +DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition + + +// fragments +fragment A : [aA]; +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; +fragment DIGIT: [0-9]; + +SPACE: [ ]+ ; +ANY: . ; \ No newline at end of file From c4d2cb77af63ab20435613bdaab8f9722b585949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Wed, 31 Jul 2024 16:58:22 +0200 Subject: [PATCH 02/27] [NAE-1997] Query language - refactor grammar --- src/main/resources/antlr4/QueryLang.g4 | 108 ++++++++++++++++++------- 1 file changed, 77 insertions(+), 31 deletions(-) diff --git a/src/main/resources/antlr4/QueryLang.g4 b/src/main/resources/antlr4/QueryLang.g4 index 65bb372d8e8..e958d314abc 100644 --- a/src/main/resources/antlr4/QueryLang.g4 +++ b/src/main/resources/antlr4/QueryLang.g4 @@ -3,39 +3,47 @@ grammar QueryLang; query: resource delimeter conditions EOF ; resource: CASE + | CASES | TASK + | TASKS | USER - | PROCESS ; - -// resource types -CASE: C A S E | C A S E S ; -TASK: T A S K | T A S K S ; -USER: U S E R | U S E R S ; -PROCESS: P R O C E S S | P R O C E S S E S ; + | USERS + | PROCESS + | PROCESSES + ; // delimeter delimeter: WHERE_DELIMETER | COLON_DELIMETER ; WHERE_DELIMETER: SPACE W H E R E SPACE ; COLON_DELIMETER: SPACE? ':' SPACE ; +// conditions conditions: orExpression ; - -orExpression: andExpression (SPACE OR SPACE andExpression)*; - -andExpression: conditionGroup (SPACE AND SPACE conditionGroup)*; - +orExpression: andExpression (SPACE OR SPACE andExpression)* ; +andExpression: conditionGroup (SPACE AND SPACE conditionGroup)* ; conditionGroup: condition | '(' SPACE? orExpression SPACE? ')' SPACE? ; - -condition: (NOT SPACE)? attribute SPACE operator SPACE value SPACE?; - -AND: A N D | '&' ; -OR: O R | '|' ; -NOT: N O T | '!' ; - -attribute: ID | TITLE; - -ID: I D ; -TITLE: T I T L E ; +condition: (NOT SPACE)? attribute SPACE operator SPACE value SPACE? ; + +attribute: ID + | TITLE + | IDENTIFIER + | VERSION + | CREATION_DATE + | PROCESS_ID + | AUTHOR + | PLACES + | TASKS + | TRANSITION_ID + | STATE + | USER_ID + | CASE_ID + | LAST_ASSIGN + | LAST_FINISH + | NAME + | SURNAME + | EMAIL + | data + ; operator: EQ | LT @@ -45,7 +53,18 @@ operator: EQ | CONTAINS ; +value: STRING + | NUMBER + | DATE + ; + +// special attribute rules +data: DATA '.' FIELD_ID '.' (VALUE | OPTIONS) ; + // operators +AND: A N D | '&' ; +OR: O R | '|' ; +NOT: N O T | '!' ; EQ: E Q | '==' ; LT: L T | '<' ; GT: G T | '>' ; @@ -53,16 +72,46 @@ LTE: L T E | '<=' ; GTE: G T E | '>=' ; CONTAINS: C O N T A I N S | '~'; -value: STRING - | NUMBER - | DATE - ; +// resurces +CASE: C A S E ; +CASES: C A S E S ; +TASK: T A S K ; +TASKS: T A S K S ; +USER: U S E R ; +USERS: U S E R S ; +PROCESS: P R O C E S S ; +PROCESSES: P R O C E S S E S ; + +// attributes +ID: I D ; +TITLE: T I T L E ; +IDENTIFIER : I D E N T I F I E R ; +VERSION: V E R S I O N ; +CREATION_DATE : C R E A T I O N D A T E ; +PROCESS_ID: P R O C E S S I D ; +AUTHOR : A U T H O R ; +PLACES : P L A C E S ; +TRANSITION_ID : T R A N S I T I O N I D ; +STATE : S T A T E ; +USER_ID : U S E R I D ; +CASE_ID : C A S E I D ; +LAST_ASSIGN : L A S T A S S I G N ; +LAST_FINISH : L A S T F I N I S H ; +NAME : N A M E ; +SURNAME : S U R N A M E ; +EMAIL : E M A I L ; + +DATA : D A T A ; +VALUE: V A L U E ; +OPTIONS: O P T I O N S ; // basic types STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; NUMBER: DIGIT+ ('.' DIGIT+)? ; DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition - +FIELD_ID: [a-zA-Z0-9\-_]+ ; +SPACE: [ ]+ ; +ANY: . ; // fragments fragment A : [aA]; @@ -92,6 +141,3 @@ fragment X : [xX]; fragment Y : [yY]; fragment Z : [zZ]; fragment DIGIT: [0-9]; - -SPACE: [ ]+ ; -ANY: . ; \ No newline at end of file From 8f1f20b0764d71ec80425234cc9ec28b14068f87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Fri, 2 Aug 2024 12:12:41 +0200 Subject: [PATCH 03/27] [NAE-1997] Query language - add more parser and lexer rules --- src/main/resources/antlr4/QueryLang.g4 | 51 ++++++++++++++++---------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/src/main/resources/antlr4/QueryLang.g4 b/src/main/resources/antlr4/QueryLang.g4 index e958d314abc..f7024302aee 100644 --- a/src/main/resources/antlr4/QueryLang.g4 +++ b/src/main/resources/antlr4/QueryLang.g4 @@ -31,8 +31,6 @@ attribute: ID | CREATION_DATE | PROCESS_ID | AUTHOR - | PLACES - | TASKS | TRANSITION_ID | STATE | USER_ID @@ -43,6 +41,8 @@ attribute: ID | SURNAME | EMAIL | data + | places + | tasks ; operator: EQ @@ -53,14 +53,20 @@ operator: EQ | CONTAINS ; +// special attribute rules +data: DATA '.' field_id=JAVA_ID '.' (VALUE | OPTIONS) ; +places: PLACES '.' place_id=JAVA_ID '.' MARKING ; +tasks: TASKS '.' task_id=JAVA_ID '.' (STATE | USER_ID) ; + value: STRING | NUMBER | DATE + | DATETIME + | VERSION_NUMBER + | LIST + | BOOLEAN ; -// special attribute rules -data: DATA '.' FIELD_ID '.' (VALUE | OPTIONS) ; - // operators AND: A N D | '&' ; OR: O R | '|' ; @@ -85,31 +91,36 @@ PROCESSES: P R O C E S S E S ; // attributes ID: I D ; TITLE: T I T L E ; -IDENTIFIER : I D E N T I F I E R ; +IDENTIFIER: I D E N T I F I E R ; VERSION: V E R S I O N ; -CREATION_DATE : C R E A T I O N D A T E ; +CREATION_DATE: C R E A T I O N D A T E ; PROCESS_ID: P R O C E S S I D ; -AUTHOR : A U T H O R ; -PLACES : P L A C E S ; -TRANSITION_ID : T R A N S I T I O N I D ; -STATE : S T A T E ; -USER_ID : U S E R I D ; -CASE_ID : C A S E I D ; -LAST_ASSIGN : L A S T A S S I G N ; -LAST_FINISH : L A S T F I N I S H ; -NAME : N A M E ; -SURNAME : S U R N A M E ; -EMAIL : E M A I L ; +AUTHOR: A U T H O R ; +PLACES: P L A C E S ; +TRANSITION_ID: T R A N S I T I O N I D ; +STATE: S T A T E ; +USER_ID: U S E R I D ; +CASE_ID: C A S E I D ; +LAST_ASSIGN: L A S T A S S I G N ; +LAST_FINISH: L A S T F I N I S H ; +NAME: N A M E ; +SURNAME: S U R N A M E ; +EMAIL: E M A I L ; -DATA : D A T A ; +DATA: D A T A ; VALUE: V A L U E ; OPTIONS: O P T I O N S ; +MARKING: M A R K I N G ; // basic types +LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; NUMBER: DIGIT+ ('.' DIGIT+)? ; +DATETIME: DATE SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition -FIELD_ID: [a-zA-Z0-9\-_]+ ; +BOOLEAN: T R U E | F A L S E ; +VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; +JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; SPACE: [ ]+ ; ANY: . ; From a48e58655d5902e7fd6cc28ea58ad8404dd0f157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Mon, 5 Aug 2024 13:22:45 +0200 Subject: [PATCH 04/27] [NAE-1997] Query language - move query lang grammar file - update antlr4 plugin config - update grammar to match resource specific queries --- pom.xml | 5 +- .../application/engine/antlr4/QueryLang.g4 | 192 ++++++++++++++++++ src/main/resources/antlr4/QueryLang.g4 | 154 -------------- 3 files changed, 195 insertions(+), 156 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 delete mode 100644 src/main/resources/antlr4/QueryLang.g4 diff --git a/pom.xml b/pom.xml index 3af8eae78d8..8aecf370588 100644 --- a/pom.xml +++ b/pom.xml @@ -792,8 +792,9 @@ antlr4-maven-plugin 4.7.1 - src/main/resources/antlr4 - ${project.build.directory}/generated-sources/java/com/netgrif/application/engine/antlr4 + src/main/java + ${project.build.directory}/generated-sources/java/ + true diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 new file mode 100644 index 00000000000..0634fdf6d47 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 @@ -0,0 +1,192 @@ +grammar QueryLang; + +query: (processQuery | caseQuery | taskQuery | userQuery) EOF ; + +processQuery: (PROCESS | PROCESSES) delimeter processConditions ; +caseQuery: (CASE | CASES) delimeter caseConditions ; +taskQuery: (TASK | TASKS) delimeter taskConditions ; +userQuery: (USER | USERS) delimeter userConditions ; + +processConditions: processOrExpression ; +processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; +processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; +processConditionGroup: processCondition | '(' SPACE? processConditions SPACE? ')' SPACE? ; +processCondition: (NOT SPACE)? processComparisons SPACE? ; + +caseConditions: caseOrExpression ; +caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; +caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; +caseConditionGroup: caseCondition | '(' SPACE? caseConditions SPACE? ')' SPACE? ; +caseCondition: (NOT SPACE)? caseComparisons SPACE? ; + +taskConditions: taskOrExpression ; +taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; +taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; +taskConditionGroup: taskCondition | '(' SPACE? taskConditions SPACE? ')' SPACE? ; +taskCondition: (NOT SPACE)? taskComparisons SPACE? ; + +userConditions: userOrExpression ; +userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; +userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; +userConditionGroup: userCondition | '(' SPACE? userConditions SPACE? ')' SPACE? ; +userCondition: (NOT SPACE)? userComparisons SPACE? ; + +// delimeter +delimeter: WHERE_DELIMETER | COLON_DELIMETER ; +WHERE_DELIMETER: SPACE W H E R E SPACE ; +COLON_DELIMETER: SPACE? ':' SPACE ; + +// resource comparisons +processComparisons: idComparison + | identifierComparison + | versionComparison + | titleComparison + | creationDateComparison + ; + +caseComparisons: idComparison + | processIdComparison + | titleComparison + | creationDateComparison + | authorComparison + | placesComparison + | tasksComparison + | dataComparison + ; + +taskComparisons: idComparison + | transitionIdComparison + | titleComparison + | stateComparison + | userIdComparison + | caseIdComparison + | processIdComparison + | lastAssignComparison + | lastFinishComparison + ; + +userComparisons: idComparison + | nameComparison + | surnameComparison + | emailComparison + ; + +// attribute comparisons +idComparison: ID SPACE stringComparison ; +titleComparison: TITLE SPACE stringComparison ; +identifierComparison: IDENTIFIER SPACE stringComparison ; +versionComparison: VERSION SPACE (EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; +creationDateComparison: CREATION_DATE SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? +processIdComparison: PROCESS_ID SPACE stringComparison ; +authorComparison: AUTHOR SPACE stringComparison ; +transitionIdComparison: TRANSITION_ID SPACE stringComparison ; +stateComparison: STATE SPACE stringComparison ; +userIdComparison: USER_ID SPACE stringComparison ; +caseIdComparison: CASE_ID SPACE stringComparison ; +lastAssignComparison: LAST_ASSIGN SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? +lastFinishComparison: LAST_FINISH SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? +nameComparison: NAME SPACE stringComparison ; +surnameComparison: SURNAME SPACE stringComparison ; +emailComparison: EMAIL SPACE stringComparison ; +dataComparison: data SPACE (stringComparison | numberComparison | dateComparison | dateTimeComparison | booleanComparison) ; +placesComparison: places SPACE numberComparison ; +tasksComparison: tasks SPACE stringComparison ; + +// basic comparisons +stringComparison: (EQ | CONTAINS) SPACE STRING ; +numberComparison: (EQ | LT | GT | LTE | GTE) SPACE NUMBER ; +dateComparison: (EQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: (EQ | LT | GT | LTE | GTE) SPACE DATETIME ; +booleanComparison: EQ SPACE BOOLEAN ; + +// special attribute rules +data: DATA '.' fieldId=JAVA_ID '.' (VALUE | OPTIONS) ; +places: PLACES '.' placeId=JAVA_ID '.' MARKING ; +tasks: TASKS '.' taskId=JAVA_ID '.' (STATE | USER_ID) ; + +// operators +AND: A N D | '&' ; +OR: O R | '|' ; +NOT: N O T | '!' ; +EQ: E Q | '==' ; +LT: L T | '<' ; +GT: G T | '>' ; +LTE: L T E | '<=' ; +GTE: G T E | '>=' ; +CONTAINS: C O N T A I N S | '~'; + +// resurces +CASE: C A S E ; +CASES: C A S E S ; +TASK: T A S K ; +TASKS: T A S K S ; +USER: U S E R ; +USERS: U S E R S ; +PROCESS: P R O C E S S ; +PROCESSES: P R O C E S S E S ; + +// attributes +ID: I D ; +TITLE: T I T L E ; +IDENTIFIER: I D E N T I F I E R ; +VERSION: V E R S I O N ; +CREATION_DATE: C R E A T I O N D A T E ; +PROCESS_ID: P R O C E S S I D ; +AUTHOR: A U T H O R ; +PLACES: P L A C E S ; +TRANSITION_ID: T R A N S I T I O N I D ; +STATE: S T A T E ; +USER_ID: U S E R I D ; +CASE_ID: C A S E I D ; +LAST_ASSIGN: L A S T A S S I G N ; +LAST_FINISH: L A S T F I N I S H ; +NAME: N A M E ; +SURNAME: S U R N A M E ; +EMAIL: E M A I L ; + +DATA: D A T A ; +VALUE: V A L U E ; +OPTIONS: O P T I O N S ; +MARKING: M A R K I N G ; + +// basic types +LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; +STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; +NUMBER: DIGIT+ ('.' DIGIT+)? ; +DATETIME: DATE SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition +DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition +BOOLEAN: T R U E | F A L S E ; +VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; +JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; + +SPACE: [ ]+ ; +ANY: . ; + +// fragments +fragment A : [aA]; +fragment B : [bB]; +fragment C : [cC]; +fragment D : [dD]; +fragment E : [eE]; +fragment F : [fF]; +fragment G : [gG]; +fragment H : [hH]; +fragment I : [iI]; +fragment J : [jJ]; +fragment K : [kK]; +fragment L : [lL]; +fragment M : [mM]; +fragment N : [nN]; +fragment O : [oO]; +fragment P : [pP]; +fragment Q : [qQ]; +fragment R : [rR]; +fragment S : [sS]; +fragment T : [tT]; +fragment U : [uU]; +fragment V : [vV]; +fragment W : [wW]; +fragment X : [xX]; +fragment Y : [yY]; +fragment Z : [zZ]; +fragment DIGIT: [0-9]; diff --git a/src/main/resources/antlr4/QueryLang.g4 b/src/main/resources/antlr4/QueryLang.g4 deleted file mode 100644 index f7024302aee..00000000000 --- a/src/main/resources/antlr4/QueryLang.g4 +++ /dev/null @@ -1,154 +0,0 @@ -grammar QueryLang; - -query: resource delimeter conditions EOF ; - -resource: CASE - | CASES - | TASK - | TASKS - | USER - | USERS - | PROCESS - | PROCESSES - ; - -// delimeter -delimeter: WHERE_DELIMETER | COLON_DELIMETER ; -WHERE_DELIMETER: SPACE W H E R E SPACE ; -COLON_DELIMETER: SPACE? ':' SPACE ; - -// conditions -conditions: orExpression ; -orExpression: andExpression (SPACE OR SPACE andExpression)* ; -andExpression: conditionGroup (SPACE AND SPACE conditionGroup)* ; -conditionGroup: condition | '(' SPACE? orExpression SPACE? ')' SPACE? ; -condition: (NOT SPACE)? attribute SPACE operator SPACE value SPACE? ; - -attribute: ID - | TITLE - | IDENTIFIER - | VERSION - | CREATION_DATE - | PROCESS_ID - | AUTHOR - | TRANSITION_ID - | STATE - | USER_ID - | CASE_ID - | LAST_ASSIGN - | LAST_FINISH - | NAME - | SURNAME - | EMAIL - | data - | places - | tasks - ; - -operator: EQ - | LT - | GT - | LTE - | GTE - | CONTAINS - ; - -// special attribute rules -data: DATA '.' field_id=JAVA_ID '.' (VALUE | OPTIONS) ; -places: PLACES '.' place_id=JAVA_ID '.' MARKING ; -tasks: TASKS '.' task_id=JAVA_ID '.' (STATE | USER_ID) ; - -value: STRING - | NUMBER - | DATE - | DATETIME - | VERSION_NUMBER - | LIST - | BOOLEAN - ; - -// operators -AND: A N D | '&' ; -OR: O R | '|' ; -NOT: N O T | '!' ; -EQ: E Q | '==' ; -LT: L T | '<' ; -GT: G T | '>' ; -LTE: L T E | '<=' ; -GTE: G T E | '>=' ; -CONTAINS: C O N T A I N S | '~'; - -// resurces -CASE: C A S E ; -CASES: C A S E S ; -TASK: T A S K ; -TASKS: T A S K S ; -USER: U S E R ; -USERS: U S E R S ; -PROCESS: P R O C E S S ; -PROCESSES: P R O C E S S E S ; - -// attributes -ID: I D ; -TITLE: T I T L E ; -IDENTIFIER: I D E N T I F I E R ; -VERSION: V E R S I O N ; -CREATION_DATE: C R E A T I O N D A T E ; -PROCESS_ID: P R O C E S S I D ; -AUTHOR: A U T H O R ; -PLACES: P L A C E S ; -TRANSITION_ID: T R A N S I T I O N I D ; -STATE: S T A T E ; -USER_ID: U S E R I D ; -CASE_ID: C A S E I D ; -LAST_ASSIGN: L A S T A S S I G N ; -LAST_FINISH: L A S T F I N I S H ; -NAME: N A M E ; -SURNAME: S U R N A M E ; -EMAIL: E M A I L ; - -DATA: D A T A ; -VALUE: V A L U E ; -OPTIONS: O P T I O N S ; -MARKING: M A R K I N G ; - -// basic types -LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; -STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; -NUMBER: DIGIT+ ('.' DIGIT+)? ; -DATETIME: DATE SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition -DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition -BOOLEAN: T R U E | F A L S E ; -VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; -JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; -SPACE: [ ]+ ; -ANY: . ; - -// fragments -fragment A : [aA]; -fragment B : [bB]; -fragment C : [cC]; -fragment D : [dD]; -fragment E : [eE]; -fragment F : [fF]; -fragment G : [gG]; -fragment H : [hH]; -fragment I : [iI]; -fragment J : [jJ]; -fragment K : [kK]; -fragment L : [lL]; -fragment M : [mM]; -fragment N : [nN]; -fragment O : [oO]; -fragment P : [pP]; -fragment Q : [qQ]; -fragment R : [rR]; -fragment S : [sS]; -fragment T : [tT]; -fragment U : [uU]; -fragment V : [vV]; -fragment W : [wW]; -fragment X : [xX]; -fragment Y : [yY]; -fragment Z : [zZ]; -fragment DIGIT: [0-9]; From 0a38365b79b3127a7ff70a0e8920c711c19e6b23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Tue, 13 Aug 2024 17:36:26 +0200 Subject: [PATCH 05/27] [NAE-1997] Query language - update grammar - partially implement query translation to querydsl --- .../application/engine/antlr4/QueryLang.g4 | 14 +- .../search/QueryLangMongoEvaluator.java | 392 ++++++++++++++++++ .../application/engine/search/QueryType.java | 23 + 3 files changed, 423 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java create mode 100644 src/main/java/com/netgrif/application/engine/search/QueryType.java diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 index 0634fdf6d47..0a097f06f27 100644 --- a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 @@ -75,12 +75,12 @@ userComparisons: idComparison idComparison: ID SPACE stringComparison ; titleComparison: TITLE SPACE stringComparison ; identifierComparison: IDENTIFIER SPACE stringComparison ; -versionComparison: VERSION SPACE (EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; +versionComparison: VERSION SPACE op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; creationDateComparison: CREATION_DATE SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? processIdComparison: PROCESS_ID SPACE stringComparison ; authorComparison: AUTHOR SPACE stringComparison ; transitionIdComparison: TRANSITION_ID SPACE stringComparison ; -stateComparison: STATE SPACE stringComparison ; +stateComparison: STATE SPACE EQ state=(ENABLED | DISABLED) ; userIdComparison: USER_ID SPACE stringComparison ; caseIdComparison: CASE_ID SPACE stringComparison ; lastAssignComparison: LAST_ASSIGN SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? @@ -93,10 +93,10 @@ placesComparison: places SPACE numberComparison ; tasksComparison: tasks SPACE stringComparison ; // basic comparisons -stringComparison: (EQ | CONTAINS) SPACE STRING ; -numberComparison: (EQ | LT | GT | LTE | GTE) SPACE NUMBER ; -dateComparison: (EQ | LT | GT | LTE | GTE) SPACE DATE ; -dateTimeComparison: (EQ | LT | GT | LTE | GTE) SPACE DATETIME ; +stringComparison: op=(EQ | CONTAINS) SPACE STRING ; +numberComparison: op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; +dateComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; booleanComparison: EQ SPACE BOOLEAN ; // special attribute rules @@ -148,6 +148,8 @@ DATA: D A T A ; VALUE: V A L U E ; OPTIONS: O P T I O N S ; MARKING: M A R K I N G ; +ENABLED: E N A B L E D ; +DISABLED: D I S A B L E D ; // basic types LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java new file mode 100644 index 00000000000..771852e6b24 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java @@ -0,0 +1,392 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.antlr4.QueryLangBaseVisitor; +import com.netgrif.application.engine.antlr4.QueryLangLexer; +import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.QVersion; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +import com.netgrif.application.engine.workflow.domain.State; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.StringPath; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; + +public class QueryLangMongoEvaluator extends QueryLangBaseVisitor { + + private QueryType type; + + @Override + public Predicate visitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.fromString("process"); + return visit(ctx.processConditions()); + } + + @Override + public Predicate visitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.fromString("case"); + return visit(ctx.caseConditions()); + } + + @Override + public Predicate visitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.fromString("task"); + return visit(ctx.taskConditions()); + } + + @Override + public Predicate visitUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.fromString("user"); + return visit(ctx.userConditions()); + } + + @Override + public Predicate visitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + if (ctx.processAndExpression().size() > 1) { +// return visit(ctx.processAndExpression(0)) | visit(ctx.processAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.processAndExpression(0)); + } + + @Override + public Predicate visitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + if (ctx.processConditionGroup().size() > 1) { +// return visit(ctx.processConditionGroup(0)) & visit(ctx.processConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.processConditionGroup(0)); + } + + @Override + public Predicate visitProcessConditionGroup(QueryLangParser.ProcessConditionGroupContext ctx) { + if (ctx.processCondition() != null) { + return visit(ctx.processCondition()); + } + return ( visit(ctx.processConditions()) ); + } + + @Override + public Predicate visitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + if (ctx.NOT() != null) { + return visit(ctx.processComparisons()).not(); + } + return visit(ctx.processComparisons()); + } + + @Override + public Predicate visitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + if (ctx.caseAndExpression().size() > 1) { +// return visit(ctx.caseAndExpression(0)) | visit(ctx.caseAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.caseAndExpression(0)); + } + + @Override + public Predicate visitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + if (ctx.caseConditionGroup().size() > 1) { +// return visit(ctx.caseConditionGroup(0)) & visit(ctx.caseConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.caseConditionGroup(0)); + } + + @Override + public Predicate visitCaseConditionGroup(QueryLangParser.CaseConditionGroupContext ctx) { + if (ctx.caseCondition() != null) { + return visit(ctx.caseCondition()); + } + return ( visit(ctx.caseConditions()) ); + } + + @Override + public Predicate visitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + if (ctx.NOT() != null) { + return visit(ctx.caseComparisons()).not(); + } + return visit(ctx.caseComparisons()); + } + + @Override + public Predicate visitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + if (ctx.taskAndExpression().size() > 1) { +// return visit(ctx.taskAndExpression(0)) | visit(ctx.taskAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.taskAndExpression(0)); + } + + @Override + public Predicate visitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + if (ctx.taskConditionGroup().size() > 1) { +// return visit(ctx.taskConditionGroup(0)) & visit(ctx.taskConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.taskConditionGroup(0)); + } + + @Override + public Predicate visitTaskConditionGroup(QueryLangParser.TaskConditionGroupContext ctx) { + if (ctx.taskCondition() != null) { + return visit(ctx.taskCondition()); + } + return ( visit(ctx.taskConditions()) ); + } + + @Override + public Predicate visitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + if (ctx.NOT() != null) { + return visit(ctx.taskComparisons()).not(); + } + return visit(ctx.taskComparisons()); + } + + @Override + public Predicate visitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + if (ctx.userAndExpression().size() > 1) { +// return visit(ctx.userAndExpression(0)) | visit(ctx.userAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.userAndExpression(0)); + } + + @Override + public Predicate visitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + if (ctx.userConditionGroup().size() > 1) { +// return visit(ctx.userConditionGroup(0)) & visit(ctx.userConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' + } + return visit(ctx.userConditionGroup(0)); + } + + @Override + public Predicate visitUserConditionGroup(QueryLangParser.UserConditionGroupContext ctx) { + if (ctx.userCondition() != null) { + return visit(ctx.userCondition()); + } + return ( visit(ctx.userConditions()) ); + } + + @Override + public Predicate visitUserCondition(QueryLangParser.UserConditionContext ctx) { + if (ctx.NOT() != null) { + return visit(ctx.userComparisons()).not(); + } + return visit(ctx.userComparisons()); + } + + @Override + public Predicate visitIdComparison(QueryLangParser.IdComparisonContext ctx) { + StringPath stringPath; + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.stringId; + break; + case CASE: + stringPath = QCase.case$.stringId; + break; + case TASK: + stringPath = QTask.task.stringId; + break; + case USER: + stringPath = QUser.user.stringId; + break; + default: + throw new UnsupportedOperationException(); + } + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { + StringPath stringPath; + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new UnsupportedOperationException(); + } + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitVersionComparison(QueryLangParser.VersionComparisonContext ctx) { + String[] versionNumber = ctx.VERSION_NUMBER().getText().split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + QVersion qVersion = QPetriNet.petriNet.version; + + switch (ctx.op.getType()) { + case QueryLangParser.EQ: + return qVersion.eq(new Version(major, minor, patch)); + case QueryLangParser.GT: + return qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); + case QueryLangParser.GTE: + return qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))) + .or(qVersion.eq(new Version(major, minor, patch))); + case QueryLangParser.LT: + return qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); + case QueryLangParser.LTE: + return qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) + .or(qVersion.eq(new Version(major, minor, patch))); + } + throw new UnsupportedOperationException(); + } + + @Override + public Predicate visitCreationDateComparison(QueryLangParser.CreationDateComparisonContext ctx) { + // todo NAE-1997: implement date/datetime comparison + return super.visitCreationDateComparison(ctx); + } + + @Override + public Predicate visitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { + StringPath stringPath; + switch (type) { + case CASE: + stringPath = QCase.case$.petriNetId; + break; + case TASK: + stringPath = QTask.task.processId; + break; + default: + throw new UnsupportedOperationException(); + } + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { + StringPath stringPath = QCase.case$.author.id; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { + StringPath stringPath = QTask.task.transitionId; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitStateComparison(QueryLangParser.StateComparisonContext ctx) { + switch (ctx.state.getType()) { + case QueryLangParser.ENABLED: + return QTask.task.state.eq(State.ENABLED); + case QueryLangParser.DISABLED: + return QTask.task.state.eq(State.DISABLED); + } + + throw new UnsupportedOperationException(); + } + + @Override + public Predicate visitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { + StringPath stringPath = QTask.task.userId; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { + StringPath stringPath = QTask.task.caseId; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitLastAssignComparison(QueryLangParser.LastAssignComparisonContext ctx) { + // todo NAE-1997: implement date/datetime comparison + return super.visitLastAssignComparison(ctx); + } + + @Override + public Predicate visitLastFinishComparison(QueryLangParser.LastFinishComparisonContext ctx) { + // todo NAE-1997: implement date/datetime comparison + return super.visitLastFinishComparison(ctx); + } + + @Override + public Predicate visitNameComparison(QueryLangParser.NameComparisonContext ctx) { + StringPath stringPath = QUser.user.name; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { + StringPath stringPath = QUser.user.surname; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { + StringPath stringPath = QUser.user.email; + String string = ctx.stringComparison().STRING().getText(); + + return evaluateStringComparison(stringPath, ctx.stringComparison()); + } + + @Override + public Predicate visitDataComparison(QueryLangParser.DataComparisonContext ctx) { + throw new UnsupportedOperationException(); + } + + @Override + public Predicate visitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { + throw new UnsupportedOperationException(); + + } + + @Override + public Predicate visitTasksComparison(QueryLangParser.TasksComparisonContext ctx) { + throw new UnsupportedOperationException(); + } + + private static Predicate evaluateStringComparison(StringPath stringPath, QueryLangParser.StringComparisonContext ctx) { + String string = ctx.STRING().getText(); + switch (ctx.op.getType()) { + case QueryLangParser.EQ: + return stringPath.eq(string); + case QueryLangParser.CONTAINS: + return stringPath.contains(string); + } + + throw new UnsupportedOperationException(); + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/QueryType.java b/src/main/java/com/netgrif/application/engine/search/QueryType.java new file mode 100644 index 00000000000..ac782e2daf8 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryType.java @@ -0,0 +1,23 @@ +package com.netgrif.application.engine.search; + +public enum QueryType { + PROCESS, + CASE, + TASK, + USER; + + public static QueryType fromString(String type) { + switch (type) { + case "process": + return PROCESS; + case "case": + return CASE; + case "task": + return TASK; + case "user": + return USER; + default: + return null; + } + } +} From b0718af049eabd54bb921e2758615e6d496a04c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Wed, 14 Aug 2024 16:31:32 +0200 Subject: [PATCH 06/27] [NAE-1997] Query language - finish implementing query translation to querydsl --- pom.xml | 5 +- .../search/QueryLangMongoEvaluator.java | 163 +++++++++++------- .../engine/search/SearchService.java | 101 +++++++++++ 3 files changed, 206 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/search/SearchService.java diff --git a/pom.xml b/pom.xml index 8aecf370588..6add8ca2c01 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,7 @@ 7.70.0.Final netgrif-oss https://sonarcloud.io + 4.13.1 @@ -510,7 +511,7 @@ org.antlr antlr4-runtime - 4.13.1 + ${antlr4.version} @@ -790,7 +791,7 @@ org.antlr antlr4-maven-plugin - 4.7.1 + ${antlr4.version} src/main/java ${project.build.directory}/generated-sources/java/ diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java index 771852e6b24..0ac83ab0d9c 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java @@ -1,7 +1,6 @@ package com.netgrif.application.engine.search; import com.netgrif.application.engine.antlr4.QueryLangBaseVisitor; -import com.netgrif.application.engine.antlr4.QueryLangLexer; import com.netgrif.application.engine.antlr4.QueryLangParser; import com.netgrif.application.engine.auth.domain.QUser; import com.netgrif.application.engine.petrinet.domain.QPetriNet; @@ -10,11 +9,19 @@ import com.netgrif.application.engine.workflow.domain.QCase; import com.netgrif.application.engine.workflow.domain.QTask; import com.netgrif.application.engine.workflow.domain.State; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; +import lombok.Getter; +import org.antlr.v4.runtime.Token; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +@Getter public class QueryLangMongoEvaluator extends QueryLangBaseVisitor { private QueryType type; @@ -45,18 +52,16 @@ public Predicate visitUserQuery(QueryLangParser.UserQueryContext ctx) { @Override public Predicate visitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { - if (ctx.processAndExpression().size() > 1) { -// return visit(ctx.processAndExpression(0)) | visit(ctx.processAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.processAndExpression(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.processAndExpression().forEach(processAndExpressionContext -> builder.or(visit(processAndExpressionContext))); + return builder; } @Override public Predicate visitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { - if (ctx.processConditionGroup().size() > 1) { -// return visit(ctx.processConditionGroup(0)) & visit(ctx.processConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.processConditionGroup(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.processConditionGroup().forEach(processConditionGroupContext -> builder.and(visit(processConditionGroupContext))); + return builder; } @Override @@ -77,18 +82,16 @@ public Predicate visitProcessCondition(QueryLangParser.ProcessConditionContext c @Override public Predicate visitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { - if (ctx.caseAndExpression().size() > 1) { -// return visit(ctx.caseAndExpression(0)) | visit(ctx.caseAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.caseAndExpression(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.caseAndExpression().forEach(caseAndExpressionContext -> builder.or(visit(caseAndExpressionContext))); + return builder; } @Override public Predicate visitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { - if (ctx.caseConditionGroup().size() > 1) { -// return visit(ctx.caseConditionGroup(0)) & visit(ctx.caseConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.caseConditionGroup(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.caseConditionGroup().forEach(caseConditionGroupContext -> builder.and(visit(caseConditionGroupContext))); + return builder; } @Override @@ -109,18 +112,16 @@ public Predicate visitCaseCondition(QueryLangParser.CaseConditionContext ctx) { @Override public Predicate visitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { - if (ctx.taskAndExpression().size() > 1) { -// return visit(ctx.taskAndExpression(0)) | visit(ctx.taskAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.taskAndExpression(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.taskAndExpression().forEach(taskAndExpressionContext -> builder.or(visit(taskAndExpressionContext))); + return builder; } @Override public Predicate visitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { - if (ctx.taskConditionGroup().size() > 1) { -// return visit(ctx.taskConditionGroup(0)) & visit(ctx.taskConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.taskConditionGroup(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.taskConditionGroup().forEach(taskConditionGroupContext -> builder.and(visit(taskConditionGroupContext))); + return builder; } @Override @@ -141,18 +142,16 @@ public Predicate visitTaskCondition(QueryLangParser.TaskConditionContext ctx) { @Override public Predicate visitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { - if (ctx.userAndExpression().size() > 1) { -// return visit(ctx.userAndExpression(0)) | visit(ctx.userAndExpression(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.userAndExpression(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.userAndExpression().forEach(userAndExpressionContext -> builder.or(visit(userAndExpressionContext))); + return builder; } @Override public Predicate visitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { - if (ctx.userConditionGroup().size() > 1) { -// return visit(ctx.userConditionGroup(0)) & visit(ctx.userConditionGroup(1)); // todo NAE-1997: Operator '|' cannot be applied to 'com. querydsl. core. types. Predicate', 'com. querydsl. core. types. Predicate' - } - return visit(ctx.userConditionGroup(0)); + BooleanBuilder builder = new BooleanBuilder(); + ctx.userConditionGroup().forEach(userConditionGroupContext -> builder.and(visit(userConditionGroupContext))); + return builder; } @Override @@ -188,9 +187,8 @@ public Predicate visitIdComparison(QueryLangParser.IdComparisonContext ctx) { stringPath = QUser.user.stringId; break; default: - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Search by id is not available for type " + type.name()); } - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -209,9 +207,8 @@ public Predicate visitTitleComparison(QueryLangParser.TitleComparisonContext ctx stringPath = QTask.task.title.defaultValue; break; default: - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Search by title is not available for type " + type.name()); } - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -219,7 +216,6 @@ public Predicate visitTitleComparison(QueryLangParser.TitleComparisonContext ctx @Override public Predicate visitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { StringPath stringPath = QPetriNet.petriNet.identifier; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -255,13 +251,24 @@ public Predicate visitVersionComparison(QueryLangParser.VersionComparisonContext .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) .or(qVersion.eq(new Version(major, minor, patch))); } - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Operator " + ctx.op.getText() + " is not available for version comparison"); } @Override public Predicate visitCreationDateComparison(QueryLangParser.CreationDateComparisonContext ctx) { - // todo NAE-1997: implement date/datetime comparison - return super.visitCreationDateComparison(ctx); + DateTimePath dateTimePath; + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + break; + default: + throw new IllegalArgumentException("Search by creation date is not available for type " + type.name()); + } + + return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); } @Override @@ -275,9 +282,8 @@ public Predicate visitProcessIdComparison(QueryLangParser.ProcessIdComparisonCon stringPath = QTask.task.processId; break; default: - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Search by process id is not available for type " + type.name()); } - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -285,7 +291,6 @@ public Predicate visitProcessIdComparison(QueryLangParser.ProcessIdComparisonCon @Override public Predicate visitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { StringPath stringPath = QCase.case$.author.id; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -293,7 +298,6 @@ public Predicate visitAuthorComparison(QueryLangParser.AuthorComparisonContext c @Override public Predicate visitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { StringPath stringPath = QTask.task.transitionId; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -307,13 +311,12 @@ public Predicate visitStateComparison(QueryLangParser.StateComparisonContext ctx return QTask.task.state.eq(State.DISABLED); } - throw new UnsupportedOperationException(); + throw new IllegalArgumentException("Invalid task state: " + ctx.state.getType()); } @Override public Predicate visitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { StringPath stringPath = QTask.task.userId; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -321,27 +324,27 @@ public Predicate visitUserIdComparison(QueryLangParser.UserIdComparisonContext c @Override public Predicate visitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { StringPath stringPath = QTask.task.caseId; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @Override public Predicate visitLastAssignComparison(QueryLangParser.LastAssignComparisonContext ctx) { - // todo NAE-1997: implement date/datetime comparison - return super.visitLastAssignComparison(ctx); + DateTimePath dateTimePath = QTask.task.lastAssigned; + + return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); } @Override public Predicate visitLastFinishComparison(QueryLangParser.LastFinishComparisonContext ctx) { - // todo NAE-1997: implement date/datetime comparison - return super.visitLastFinishComparison(ctx); + DateTimePath dateTimePath = QTask.task.lastFinished; + + return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); } @Override public Predicate visitNameComparison(QueryLangParser.NameComparisonContext ctx) { StringPath stringPath = QUser.user.name; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -349,7 +352,6 @@ public Predicate visitNameComparison(QueryLangParser.NameComparisonContext ctx) @Override public Predicate visitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { StringPath stringPath = QUser.user.surname; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @@ -357,25 +359,24 @@ public Predicate visitSurnameComparison(QueryLangParser.SurnameComparisonContext @Override public Predicate visitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { StringPath stringPath = QUser.user.email; - String string = ctx.stringComparison().STRING().getText(); return evaluateStringComparison(stringPath, ctx.stringComparison()); } @Override public Predicate visitDataComparison(QueryLangParser.DataComparisonContext ctx) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Case search by data is not available for MongoDB"); } @Override public Predicate visitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Case search by place is not available for MongoDB"); } @Override public Predicate visitTasksComparison(QueryLangParser.TasksComparisonContext ctx) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Case search by tasks is not available for MongoDB"); } private static Predicate evaluateStringComparison(StringPath stringPath, QueryLangParser.StringComparisonContext ctx) { @@ -387,6 +388,46 @@ private static Predicate evaluateStringComparison(StringPath stringPath, QueryLa return stringPath.contains(string); } - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Operator " + ctx.op.getText() + " is not available for string comparison"); + } + + private static Predicate evaluateDateOrDateTimeComparison(DateTimePath dateTimePath, QueryLangParser.DateComparisonContext dateComparisonContext, QueryLangParser.DateTimeComparisonContext dateTimeComparisonContext) { + if (dateComparisonContext != null) { + return getDateTimePredicate(dateTimePath, dateComparisonContext.op, dateComparisonContext.DATE().getText()); + } else if (dateTimeComparisonContext != null) { + return getDateTimePredicate(dateTimePath, dateTimeComparisonContext.op, dateTimeComparisonContext.DATETIME().getText()); + } + throw new IllegalArgumentException("Date or date time comparison expected"); + } + + private static Predicate getDateTimePredicate(DateTimePath dateTimePath, Token op, String input) { + LocalDateTime localDateTime = getLocalDateTime(input); + + switch (op.getType()) { + case QueryLangParser.EQ: + return dateTimePath.eq(localDateTime); + case QueryLangParser.LT: + return dateTimePath.lt(localDateTime); + case QueryLangParser.LTE: + return dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); + case QueryLangParser.GT: + return dateTimePath.gt(localDateTime); + case QueryLangParser.GTE: + return dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); + } + + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); + } + + private static LocalDateTime getLocalDateTime(String input) { + try { + return LocalDateTime.parse(input, DateTimeFormatter.ofPattern(SearchService.DATE_TIME_PATTERN)); + } catch (DateTimeParseException ignored) { + try { + return LocalDate.parse(input, DateTimeFormatter.ofPattern(SearchService.DATE_PATTERN)).atStartOfDay(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format"); + } + } } } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java new file mode 100644 index 00000000000..eb1d726aa77 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -0,0 +1,101 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.antlr4.QueryLangLexer; +import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.auth.domain.repositories.UserRepository; +import com.netgrif.application.engine.elastic.service.ElasticCaseService; +import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; +import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; +import com.netgrif.application.engine.workflow.domain.repositories.TaskRepository; +import com.querydsl.core.types.Predicate; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTree; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Slf4j +@Service +@RequiredArgsConstructor +public class SearchService { + + public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + + private ElasticCaseService elasticCaseService; + + private CaseRepository caseRepository; + + private TaskRepository taskRepository; + + private PetriNetRepository petriNetRepository; + + private UserRepository userRepository; + + private static ParseTree getParseTree(String input) { + if (input == null || input.isEmpty()) { + return null; + } + + QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + QueryLangParser parser = new QueryLangParser(tokens); + + return parser.query(); + } + + public Object search(String input) { + ParseTree tree = getParseTree(input); + // todo NAE-1997: implement actual search + return null; + } + + public long count(String input) { + ParseTree tree = getParseTree(input); + // todo NAE-1997: implement actual count + QueryLangMongoEvaluator evaluator = new QueryLangMongoEvaluator(); + + Predicate predicate; + try { + predicate = evaluator.visit(tree); + switch (evaluator.getType()) { + case PROCESS: + return petriNetRepository.count(predicate); + case CASE: + return caseRepository.count(predicate); + case TASK: + return taskRepository.count(predicate); + case USER: + return userRepository.count(predicate); + } + } catch (UnsupportedOperationException e) { + // todo NAE-1997: count with elastic? + log.error(e.getMessage()); + } + + + return 0; + } + + public static String convertDate(LocalDate localDate) { + return localDate.format(DateTimeFormatter.ofPattern(DATE_PATTERN)); + } + + public static String convertDate(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ofPattern(DATE_PATTERN)); + } + + public static String convertDateTime(LocalDate localDate) { + return localDate.atStartOfDay().format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); + } + + public static String convertDateTime(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); + } + +} From aecbe3751a14106af579bbcb0cde42fe275902cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Fri, 16 Aug 2024 17:43:44 +0200 Subject: [PATCH 07/27] [NAE-1997] Query language - update grammar - create SearchUtils for constants and helper methods --- .../application/engine/antlr4/QueryLang.g4 | 32 ++++-- .../engine/search/SearchService.java | 39 ++----- .../engine/search/SearchUtils.java | 101 ++++++++++++++++++ 3 files changed, 129 insertions(+), 43 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/search/SearchUtils.java diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 index 0a097f06f27..7a3bcc0adc4 100644 --- a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 @@ -1,11 +1,10 @@ grammar QueryLang; -query: (processQuery | caseQuery | taskQuery | userQuery) EOF ; - -processQuery: (PROCESS | PROCESSES) delimeter processConditions ; -caseQuery: (CASE | CASES) delimeter caseConditions ; -taskQuery: (TASK | TASKS) delimeter taskConditions ; -userQuery: (USER | USERS) delimeter userConditions ; +query: resource=(PROCESS | PROCESSES) delimeter processConditions EOF # processQuery + | resource=(CASE | CASES) delimeter caseConditions EOF # caseQuery + | resource=(TASK | TASKS) delimeter taskConditions EOF # taskQuery + | resource=(USER | USERS) delimeter userConditions EOF # userQuery + ; processConditions: processOrExpression ; processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; @@ -76,19 +75,30 @@ idComparison: ID SPACE stringComparison ; titleComparison: TITLE SPACE stringComparison ; identifierComparison: IDENTIFIER SPACE stringComparison ; versionComparison: VERSION SPACE op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; -creationDateComparison: CREATION_DATE SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? +creationDateComparison: CREATION_DATE SPACE dateComparison # cdDate + | CREATION_DATE SPACE dateTimeComparison # cdDateTime + ; // todo NAE-1997: date/datetime? processIdComparison: PROCESS_ID SPACE stringComparison ; authorComparison: AUTHOR SPACE stringComparison ; transitionIdComparison: TRANSITION_ID SPACE stringComparison ; stateComparison: STATE SPACE EQ state=(ENABLED | DISABLED) ; userIdComparison: USER_ID SPACE stringComparison ; caseIdComparison: CASE_ID SPACE stringComparison ; -lastAssignComparison: LAST_ASSIGN SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? -lastFinishComparison: LAST_FINISH SPACE (dateComparison | dateTimeComparison) ; // todo NAE-1997: date/datetime? +lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDate + | LAST_ASSIGN SPACE dateTimeComparison # laDateTime + ; // todo NAE-1997: date/datetime? +lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDate + | LAST_FINISH SPACE dateTimeComparison # lfDateTime + ; // todo NAE-1997: date/datetime? nameComparison: NAME SPACE stringComparison ; surnameComparison: SURNAME SPACE stringComparison ; emailComparison: EMAIL SPACE stringComparison ; -dataComparison: data SPACE (stringComparison | numberComparison | dateComparison | dateTimeComparison | booleanComparison) ; +dataComparison: data SPACE stringComparison # dataString + | data SPACE numberComparison # dataNumber + | data SPACE dateComparison # dataDate + | data SPACE dateTimeComparison # dataDatetime + | data SPACE booleanComparison # dataBoolean + ; placesComparison: places SPACE numberComparison ; tasksComparison: tasks SPACE stringComparison ; @@ -155,7 +165,7 @@ DISABLED: D I S A B L E D ; LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; NUMBER: DIGIT+ ('.' DIGIT+)? ; -DATETIME: DATE SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition +DATETIME: DATE 'T' SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition BOOLEAN: T R U E | F A L S E ; VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index eb1d726aa77..f4a3400e4fc 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -3,7 +3,7 @@ import com.netgrif.application.engine.antlr4.QueryLangLexer; import com.netgrif.application.engine.antlr4.QueryLangParser; import com.netgrif.application.engine.auth.domain.repositories.UserRepository; -import com.netgrif.application.engine.elastic.service.ElasticCaseService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; import com.netgrif.application.engine.workflow.domain.repositories.TaskRepository; @@ -15,27 +15,20 @@ import org.antlr.v4.runtime.tree.ParseTree; import org.springframework.stereotype.Service; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - @Slf4j @Service @RequiredArgsConstructor public class SearchService { - public static final String DATE_PATTERN = "yyyy-MM-dd"; - public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; - - private ElasticCaseService elasticCaseService; + private final IElasticCaseService elasticCaseService; - private CaseRepository caseRepository; + private final CaseRepository caseRepository; - private TaskRepository taskRepository; + private final TaskRepository taskRepository; - private PetriNetRepository petriNetRepository; + private final PetriNetRepository petriNetRepository; - private UserRepository userRepository; + private final UserRepository userRepository; private static ParseTree getParseTree(String input) { if (input == null || input.isEmpty()) { @@ -45,7 +38,6 @@ private static ParseTree getParseTree(String input) { QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); CommonTokenStream tokens = new CommonTokenStream(lexer); QueryLangParser parser = new QueryLangParser(tokens); - return parser.query(); } @@ -60,9 +52,8 @@ public long count(String input) { // todo NAE-1997: implement actual count QueryLangMongoEvaluator evaluator = new QueryLangMongoEvaluator(); - Predicate predicate; try { - predicate = evaluator.visit(tree); + Predicate predicate = evaluator.visit(tree); switch (evaluator.getType()) { case PROCESS: return petriNetRepository.count(predicate); @@ -82,20 +73,4 @@ public long count(String input) { return 0; } - public static String convertDate(LocalDate localDate) { - return localDate.format(DateTimeFormatter.ofPattern(DATE_PATTERN)); - } - - public static String convertDate(LocalDateTime localDateTime) { - return localDateTime.format(DateTimeFormatter.ofPattern(DATE_PATTERN)); - } - - public static String convertDateTime(LocalDate localDate) { - return localDate.atStartOfDay().format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); - } - - public static String convertDateTime(LocalDateTime localDateTime) { - return localDateTime.format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)); - } - } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java new file mode 100644 index 00000000000..9d0d0ae84ac --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java @@ -0,0 +1,101 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import org.antlr.v4.runtime.Token; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +public class SearchUtils { + + public static String toDateString(LocalDate localDate) { + return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + public static String toDateTimeString(LocalDate localDate) { + return localDate.atStartOfDay().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static String toDateTimeString(LocalDateTime localDateTime) { + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } + + public static LocalDateTime toDateTime(String dateTimeString) { + try { + return LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); + } catch (DateTimeParseException ignored) { + try { + return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format"); + } + } + } + + public static LocalDate toDate(String dateString) { + try { + return LocalDate.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE); + } catch (DateTimeParseException ignored) { + try { + return LocalDateTime.parse(dateString, DateTimeFormatter.ISO_LOCAL_DATE_TIME).toLocalDate(); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Invalid date/datetime format"); + } + } + } + + private static Predicate buildStringPredicate(StringPath stringPath, Token op, String string) { + switch (op.getType()) { + case QueryLangParser.EQ: + return stringPath.eq(string); + case QueryLangParser.CONTAINS: + return stringPath.contains(string); + } + + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for string comparison"); + } + + public static Predicate buildDateTimePredicate(DateTimePath dateTimePath, Token op, LocalDateTime localDateTime) { + switch (op.getType()) { + case QueryLangParser.EQ: + return dateTimePath.eq(localDateTime); + case QueryLangParser.LT: + return dateTimePath.lt(localDateTime); + case QueryLangParser.LTE: + return dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); + case QueryLangParser.GT: + return dateTimePath.gt(localDateTime); + case QueryLangParser.GTE: + return dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); + } + + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); + } + + public static String getElasticQuery(String attribute, Token op, String value) { + switch (op.getType()) { + case QueryLangParser.EQ: + return attribute + ":" + value; + case QueryLangParser.LT: + return attribute + ":<" + value; + case QueryLangParser.LTE: + return attribute + ":<=" + value; + case QueryLangParser.GT: + return attribute + ":>" + value; + case QueryLangParser.GTE: + return attribute + ":>=" + value; + case QueryLangParser.CONTAINS: + return attribute + ":*" + value + "*"; + } + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for elastic comparison"); + } +} From d9bc81fd0706a41a6736e57089f4e9686716440f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Mon, 19 Aug 2024 17:03:14 +0200 Subject: [PATCH 08/27] [NAE-1997] Query language - replace antlr4 visitor with antlr4 listener - annotating parse tree strategy - refactor --- .../application/engine/antlr4/QueryLang.g4 | 10 +- .../engine/search/QueryLangEvaluator.java | 638 ++++++++++++++++++ .../application/engine/search/QueryType.java | 23 - .../engine/search/SearchService.java | 15 +- .../engine/search/SearchUtils.java | 65 +- .../engine/search/enums/ComparisonType.java | 10 + .../engine/search/enums/QueryType.java | 8 + 7 files changed, 734 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java delete mode 100644 src/main/java/com/netgrif/application/engine/search/QueryType.java create mode 100644 src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java create mode 100644 src/main/java/com/netgrif/application/engine/search/enums/QueryType.java diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 index 7a3bcc0adc4..482a87455a0 100644 --- a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 @@ -107,12 +107,12 @@ stringComparison: op=(EQ | CONTAINS) SPACE STRING ; numberComparison: op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; dateComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; dateTimeComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; -booleanComparison: EQ SPACE BOOLEAN ; +booleanComparison: op=EQ SPACE BOOLEAN ; // special attribute rules -data: DATA '.' fieldId=JAVA_ID '.' (VALUE | OPTIONS) ; -places: PLACES '.' placeId=JAVA_ID '.' MARKING ; -tasks: TASKS '.' taskId=JAVA_ID '.' (STATE | USER_ID) ; +data: DATA '.' fieldId=JAVA_ID '.' property=(VALUE | OPTIONS) ; +places: PLACES '.' placeId=JAVA_ID '.' MARKING ; // todo NAE-1997: places structure in elastic +tasks: TASKS '.' taskId=JAVA_ID '.' property=(STATE | USER_ID) ; // todo NAE-1997: tasks structure in elastic, state comparison? // operators AND: A N D | '&' ; @@ -165,7 +165,7 @@ DISABLED: D I S A B L E D ; LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; NUMBER: DIGIT+ ('.' DIGIT+)? ; -DATETIME: DATE 'T' SPACE DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ; // 2020-03-03 20:00:00 todo NAE-1997 better recognition +DATETIME: DATE 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00 todo NAE-1997 better recognition DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition BOOLEAN: T R U E | F A L S E ; VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java new file mode 100644 index 00000000000..d52a9edf7bc --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -0,0 +1,638 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.search.enums.ComparisonType; +import com.netgrif.application.engine.search.enums.QueryType; +import com.netgrif.application.engine.workflow.domain.QCase; +import com.netgrif.application.engine.workflow.domain.QTask; +import com.netgrif.application.engine.workflow.domain.State; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; +import lombok.Getter; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import static com.netgrif.application.engine.search.SearchUtils.*; + +public class QueryLangEvaluator extends QueryLangBaseListener { + + ParseTreeProperty elasticQuery = new ParseTreeProperty<>(); + ParseTreeProperty mongoQuery = new ParseTreeProperty<>(); + + @Getter + private QueryType type; + @Getter + private Boolean multiple; + + public void setElasticQuery(ParseTree node, String query) { + elasticQuery.put(node, query); + } + + public String getElasticQuery(ParseTree node) { + return elasticQuery.get(node); + } + + public void setMongoQuery(ParseTree node, Predicate predicate) { + mongoQuery.put(node, predicate); + } + + public Predicate getMongoQuery(ParseTree node) { + return mongoQuery.get(node); + } + + private void processBasicExpression(ParseTree child, ParseTree current) { + setMongoQuery(current, getMongoQuery(child)); + setElasticQuery(current, getElasticQuery(child)); + } + + private void processOrExpression(List children, ParseTree current) { + List predicates = children.stream().map(this::getMongoQuery).collect(Collectors.toList()); + String elasticQuery = children.stream().map(this::getElasticQuery).collect(Collectors.joining(" OR ")); + + if (predicates.contains(null)) { + setMongoQuery(current, null); + } else { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::or); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery); + } + + private void processAndExpression(List children, ParseTree current) { + List predicates = children.stream().map(this::getMongoQuery).collect(Collectors.toList()); + String elasticQuery = children.stream().map(this::getElasticQuery).collect(Collectors.joining(" AND ")); + + if (predicates.contains(null)) { + setMongoQuery(current, null); + } else { + BooleanBuilder predicate = new BooleanBuilder(); + predicates.forEach(predicate::and); + setMongoQuery(current, predicate); + } + setElasticQuery(current, elasticQuery); + } + + private void processConditionGroup(ParseTree child, ParseTree current) { + Predicate predicate = getMongoQuery(child); + String elasticQuery = getElasticQuery(child); + + setMongoQuery(current, (predicate)); + setElasticQuery(current, "(" + elasticQuery + ")"); + } + + private void processCondition(ParseTree child, ParseTree current, Boolean not) { + Predicate predicate = getMongoQuery(child); + String elasticQuery = getElasticQuery(child); + + if (not) { + predicate = predicate != null ? predicate.not() : null; + elasticQuery = "NOT " + elasticQuery; + } + + setMongoQuery(current, predicate); + setElasticQuery(current, elasticQuery); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + processBasicExpression(ctx.processConditions(), ctx); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + processBasicExpression(ctx.caseConditions(), ctx); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + processBasicExpression(ctx.taskConditions(), ctx); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + processBasicExpression(ctx.userConditions(), ctx); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + processBasicExpression(ctx.processOrExpression(), ctx); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitProcessConditionGroup(QueryLangParser.ProcessConditionGroupContext ctx) { + processConditionGroup(ctx.processCondition() != null ? ctx.processCondition() : ctx.processConditions(), ctx); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + processCondition(ctx.processComparisons(), ctx, ctx.NOT() != null); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + processBasicExpression(ctx.caseOrExpression(), ctx); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitCaseConditionGroup(QueryLangParser.CaseConditionGroupContext ctx) { + processConditionGroup(ctx.caseCondition() != null ? ctx.caseCondition() : ctx.caseConditions(), ctx); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + processCondition(ctx.caseComparisons(), ctx, ctx.NOT() != null); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + processBasicExpression(ctx.taskOrExpression(), ctx); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitTaskConditionGroup(QueryLangParser.TaskConditionGroupContext ctx) { + processConditionGroup(ctx.taskCondition() != null ? ctx.taskCondition() : ctx.taskConditions(), ctx); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + processCondition(ctx.taskComparisons(), ctx, ctx.NOT() != null); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + processBasicExpression(ctx.userOrExpression(), ctx); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processOrExpression(children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processAndExpression(children, ctx); + } + + @Override + public void exitUserConditionGroup(QueryLangParser.UserConditionGroupContext ctx) { + processConditionGroup(ctx.userCondition() != null ? ctx.userCondition() : ctx.userConditions(), ctx); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + processCondition(ctx.userComparisons(), ctx, ctx.NOT() != null); + } + + @Override + public void exitProcessComparisons(QueryLangParser.ProcessComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitCaseComparisons(QueryLangParser.CaseComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitTaskComparisons(QueryLangParser.TaskComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { + processBasicExpression(ctx.children.get(0), ctx); + } + + @Override + public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { + StringPath stringPath; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.stringId; + break; + case CASE: + stringPath = QCase.case$.stringId; + setElasticQuery(ctx, buildElasticQuery("stringId", op, string)); + break; + case TASK: + stringPath = QTask.task.stringId; + break; + case USER: + stringPath = QUser.user.stringId; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { + StringPath stringPath; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQuery("title", op, string)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitVersionComparison(QueryLangParser.VersionComparisonContext ctx) { + Token op = ctx.op; + String versionString = ctx.VERSION_NUMBER().getText(); + + setMongoQuery(ctx, buildVersionPredicate(op, versionString)); + } + + @Override + public void exitCdDate(QueryLangParser.CdDateContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { + DateTimePath dateTimePath; + Token op = ctx.dateTimeComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { + StringPath stringPath; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + switch (type) { + case CASE: + stringPath = QCase.case$.petriNetId; + setElasticQuery(ctx, buildElasticQuery("processId", op, string)); + break; + case TASK: + stringPath = QTask.task.processId; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { + StringPath stringPath = QCase.case$.author.id; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setElasticQuery(ctx, buildElasticQuery("author", op, string)); + } + + @Override + public void exitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { + StringPath stringPath = QTask.task.transitionId; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { + switch (ctx.state.getType()) { + case QueryLangParser.ENABLED: + setMongoQuery(ctx, QTask.task.state.eq(State.ENABLED)); + case QueryLangParser.DISABLED: + setMongoQuery(ctx, QTask.task.state.eq(State.DISABLED)); + } + } + + @Override + public void exitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { + StringPath stringPath = QTask.task.userId; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { + StringPath stringPath = QTask.task.caseId; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitLaDate(QueryLangParser.LaDateContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + Token op = ctx.dateComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitLaDateTime(QueryLangParser.LaDateTimeContext ctx) { + DateTimePath dateTimePath = QTask.task.lastAssigned; + Token op = ctx.dateTimeComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitLfDate(QueryLangParser.LfDateContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + Token op = ctx.dateComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitLfDateTime(QueryLangParser.LfDateTimeContext ctx) { + DateTimePath dateTimePath = QTask.task.lastFinished; + Token op = ctx.dateTimeComparison().op; + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + } + + @Override + public void exitNameComparison(QueryLangParser.NameComparisonContext ctx) { + StringPath stringPath = QUser.user.name; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { + StringPath stringPath = QUser.user.surname; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { + StringPath stringPath = QUser.user.email; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { // todo NAE-1997: options comparison grammar update?? + String fieldId = ctx.data().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op, string)); + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { + throw new IllegalArgumentException("Search by number value is not applicable for options."); + } + + String fieldId = ctx.data().fieldId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + String number = ctx.numberComparison().NUMBER().getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op, number)); + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { + throw new IllegalArgumentException("Search by date value is not applicable for options."); + } + + String fieldId = ctx.data().fieldId.getText(); + Token op = ctx.dateComparison().op; + checkOp(ComparisonType.DATE, op); + LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, Timestamp.valueOf(localDateTime).toString())); + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { + throw new IllegalArgumentException("Search by date time value is not applicable for options."); + } + + String fieldId = ctx.data().fieldId.getText(); + Token op = ctx.dateTimeComparison().op; + checkOp(ComparisonType.DATETIME, op); + LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, Timestamp.valueOf(localDateTime).toString())); + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { + throw new IllegalArgumentException("Search by boolean value is not applicable for options."); + } + + String fieldId = ctx.data().fieldId.getText(); + Token op = ctx.booleanComparison().op; + checkOp(ComparisonType.BOOLEAN, op); + String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, Timestamp.valueOf(booleanValue).toString())); + } + + @Override + public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { + String placeId = ctx.places().placeId.getText(); + Token op = ctx.numberComparison().op; + checkOp(ComparisonType.NUMBER, op); + String numberValue = ctx.numberComparison().NUMBER().getText(); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue)); // todo NAE-1997: update places structure in elastic/another approach? + } + + @Override + public void exitTasksComparison(QueryLangParser.TasksComparisonContext ctx) { + String taskId = ctx.tasks().taskId.getText(); + String property = ctx.tasks().property.getType() == QueryLangParser.STATE ? ".state" : "userId"; + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.STRING, op); + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + property, op, string)); // todo NAE-1997: update tasks structure in elastic/another approach? + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/QueryType.java b/src/main/java/com/netgrif/application/engine/search/QueryType.java deleted file mode 100644 index ac782e2daf8..00000000000 --- a/src/main/java/com/netgrif/application/engine/search/QueryType.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.netgrif.application.engine.search; - -public enum QueryType { - PROCESS, - CASE, - TASK, - USER; - - public static QueryType fromString(String type) { - switch (type) { - case "process": - return PROCESS; - case "case": - return CASE; - case "task": - return TASK; - case "user": - return USER; - default: - return null; - } - } -} diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index f4a3400e4fc..5d7c47680ae 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -13,6 +13,7 @@ import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.springframework.stereotype.Service; @Slf4j @@ -50,11 +51,15 @@ public Object search(String input) { public long count(String input) { ParseTree tree = getParseTree(input); // todo NAE-1997: implement actual count - QueryLangMongoEvaluator evaluator = new QueryLangMongoEvaluator(); + ParseTreeWalker walker = new ParseTreeWalker(); + QueryLangEvaluator evaluator2 = new QueryLangEvaluator(); try { - Predicate predicate = evaluator.visit(tree); - switch (evaluator.getType()) { + walker.walk(evaluator2, tree); + + Predicate predicate = evaluator2.getMongoQuery(tree); + String elasticQuery = evaluator2.getElasticQuery(tree); + switch (evaluator2.getType()) { case PROCESS: return petriNetRepository.count(predicate); case CASE: @@ -64,7 +69,9 @@ public long count(String input) { case USER: return userRepository.count(predicate); } - } catch (UnsupportedOperationException e) { + + + } catch (UnsupportedOperationException | IllegalArgumentException e) { // todo NAE-1997: count with elastic? log.error(e.getMessage()); } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java index 9d0d0ae84ac..d7b9c7657f4 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java @@ -1,6 +1,10 @@ package com.netgrif.application.engine.search; import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.QVersion; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.search.enums.ComparisonType; import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; @@ -10,9 +14,21 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; public class SearchUtils { + public static final Map> comparisonOperators = Map.of( + ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS), + ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ), + ComparisonType.OPTIONS, List.of(QueryLangParser.CONTAINS) + ); + public static String toDateString(LocalDate localDate) { return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); } @@ -26,7 +42,7 @@ public static String toDateTimeString(LocalDate localDate) { } public static String toDateTimeString(LocalDateTime localDateTime) { - return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return localDateTime.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } public static LocalDateTime toDateTime(String dateTimeString) { @@ -53,7 +69,17 @@ public static LocalDate toDate(String dateString) { } } - private static Predicate buildStringPredicate(StringPath stringPath, Token op, String string) { + public static String getStringValue(String queryLangString) { + return queryLangString.replace("'", ""); + } + + public static void checkOp(ComparisonType type, Token op) { + if (!comparisonOperators.get(type).contains(op.getType())) { + throw new IllegalArgumentException("Operator " + op.getText() + " is not applicable for type " + type.toString()); + } + } + + public static Predicate buildStringPredicate(StringPath stringPath, Token op, String string) { switch (op.getType()) { case QueryLangParser.EQ: return stringPath.eq(string); @@ -64,6 +90,39 @@ private static Predicate buildStringPredicate(StringPath stringPath, Token op, S throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for string comparison"); } + public static Predicate buildVersionPredicate(Token op, String versionString) { + String[] versionNumber = versionString.split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + QVersion qVersion = QPetriNet.petriNet.version; + + switch (op.getType()) { + case QueryLangParser.EQ: + return qVersion.eq(new Version(major, minor, patch)); + case QueryLangParser.GT: + return qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); + case QueryLangParser.GTE: + return qVersion.major.gt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))) + .or(qVersion.eq(new Version(major, minor, patch))); + case QueryLangParser.LT: + return qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); + case QueryLangParser.LTE: + return qVersion.major.lt(major) + .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) + .or(qVersion.eq(new Version(major, minor, patch))); + } + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for version comparison"); + } + public static Predicate buildDateTimePredicate(DateTimePath dateTimePath, Token op, LocalDateTime localDateTime) { switch (op.getType()) { case QueryLangParser.EQ: @@ -81,7 +140,7 @@ public static Predicate buildDateTimePredicate(DateTimePath dateT throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); } - public static String getElasticQuery(String attribute, Token op, String value) { + public static String buildElasticQuery(String attribute, Token op, String value) { switch (op.getType()) { case QueryLangParser.EQ: return attribute + ":" + value; diff --git a/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java new file mode 100644 index 00000000000..27494d99a35 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java @@ -0,0 +1,10 @@ +package com.netgrif.application.engine.search.enums; + +public enum ComparisonType { + STRING, + NUMBER, + DATE, + DATETIME, + BOOLEAN, + OPTIONS +} diff --git a/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java b/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java new file mode 100644 index 00000000000..10453af537f --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/enums/QueryType.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.search.enums; + +public enum QueryType { + PROCESS, + CASE, + TASK, + USER +} From c65c5272d2b1ac92adb08e805fbbe3eda57775e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Tue, 20 Aug 2024 15:27:22 +0200 Subject: [PATCH 09/27] [NAE-1997] Query language - implement search and count methods in search service - add action delegate methods for count and search --- .../logic/action/ActionDelegate.groovy | 12 + .../search/QueryLangMongoEvaluator.java | 433 ------------------ .../engine/search/SearchService.java | 114 ++++- .../search/interfaces/ISearchService.java | 8 + 4 files changed, 115 insertions(+), 452 deletions(-) delete mode 100644 src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java create mode 100644 src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index f00157c40fa..bf3365ed7c5 100644 --- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -32,6 +32,7 @@ import com.netgrif.application.engine.petrinet.domain.version.Version import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService import com.netgrif.application.engine.petrinet.service.interfaces.IUriService import com.netgrif.application.engine.rules.domain.RuleRepository +import com.netgrif.application.engine.search.interfaces.ISearchService import com.netgrif.application.engine.startup.DefaultFiltersRunner import com.netgrif.application.engine.startup.FilterRunner import com.netgrif.application.engine.utils.FullPageRequest @@ -177,6 +178,9 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { @Autowired PublicViewProperties publicViewProperties + @Autowired + ISearchService searchService + FrontendActionOutcome Frontend /** @@ -2321,4 +2325,12 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { Case taskCase = workflowService.findOne(task.caseId) return taskCase.getPetriNet().getDataSet().get(fieldId) } + + long count(String query) { + return searchService.count(query) + } + + def search(String query) { + return searchService.search(query) + } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java deleted file mode 100644 index 0ac83ab0d9c..00000000000 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangMongoEvaluator.java +++ /dev/null @@ -1,433 +0,0 @@ -package com.netgrif.application.engine.search; - -import com.netgrif.application.engine.antlr4.QueryLangBaseVisitor; -import com.netgrif.application.engine.antlr4.QueryLangParser; -import com.netgrif.application.engine.auth.domain.QUser; -import com.netgrif.application.engine.petrinet.domain.QPetriNet; -import com.netgrif.application.engine.petrinet.domain.version.QVersion; -import com.netgrif.application.engine.petrinet.domain.version.Version; -import com.netgrif.application.engine.workflow.domain.QCase; -import com.netgrif.application.engine.workflow.domain.QTask; -import com.netgrif.application.engine.workflow.domain.State; -import com.querydsl.core.BooleanBuilder; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.DateTimePath; -import com.querydsl.core.types.dsl.StringPath; -import lombok.Getter; -import org.antlr.v4.runtime.Token; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; - -@Getter -public class QueryLangMongoEvaluator extends QueryLangBaseVisitor { - - private QueryType type; - - @Override - public Predicate visitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { - type = QueryType.fromString("process"); - return visit(ctx.processConditions()); - } - - @Override - public Predicate visitCaseQuery(QueryLangParser.CaseQueryContext ctx) { - type = QueryType.fromString("case"); - return visit(ctx.caseConditions()); - } - - @Override - public Predicate visitTaskQuery(QueryLangParser.TaskQueryContext ctx) { - type = QueryType.fromString("task"); - return visit(ctx.taskConditions()); - } - - @Override - public Predicate visitUserQuery(QueryLangParser.UserQueryContext ctx) { - type = QueryType.fromString("user"); - return visit(ctx.userConditions()); - } - - @Override - public Predicate visitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.processAndExpression().forEach(processAndExpressionContext -> builder.or(visit(processAndExpressionContext))); - return builder; - } - - @Override - public Predicate visitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.processConditionGroup().forEach(processConditionGroupContext -> builder.and(visit(processConditionGroupContext))); - return builder; - } - - @Override - public Predicate visitProcessConditionGroup(QueryLangParser.ProcessConditionGroupContext ctx) { - if (ctx.processCondition() != null) { - return visit(ctx.processCondition()); - } - return ( visit(ctx.processConditions()) ); - } - - @Override - public Predicate visitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { - if (ctx.NOT() != null) { - return visit(ctx.processComparisons()).not(); - } - return visit(ctx.processComparisons()); - } - - @Override - public Predicate visitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.caseAndExpression().forEach(caseAndExpressionContext -> builder.or(visit(caseAndExpressionContext))); - return builder; - } - - @Override - public Predicate visitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.caseConditionGroup().forEach(caseConditionGroupContext -> builder.and(visit(caseConditionGroupContext))); - return builder; - } - - @Override - public Predicate visitCaseConditionGroup(QueryLangParser.CaseConditionGroupContext ctx) { - if (ctx.caseCondition() != null) { - return visit(ctx.caseCondition()); - } - return ( visit(ctx.caseConditions()) ); - } - - @Override - public Predicate visitCaseCondition(QueryLangParser.CaseConditionContext ctx) { - if (ctx.NOT() != null) { - return visit(ctx.caseComparisons()).not(); - } - return visit(ctx.caseComparisons()); - } - - @Override - public Predicate visitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.taskAndExpression().forEach(taskAndExpressionContext -> builder.or(visit(taskAndExpressionContext))); - return builder; - } - - @Override - public Predicate visitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.taskConditionGroup().forEach(taskConditionGroupContext -> builder.and(visit(taskConditionGroupContext))); - return builder; - } - - @Override - public Predicate visitTaskConditionGroup(QueryLangParser.TaskConditionGroupContext ctx) { - if (ctx.taskCondition() != null) { - return visit(ctx.taskCondition()); - } - return ( visit(ctx.taskConditions()) ); - } - - @Override - public Predicate visitTaskCondition(QueryLangParser.TaskConditionContext ctx) { - if (ctx.NOT() != null) { - return visit(ctx.taskComparisons()).not(); - } - return visit(ctx.taskComparisons()); - } - - @Override - public Predicate visitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.userAndExpression().forEach(userAndExpressionContext -> builder.or(visit(userAndExpressionContext))); - return builder; - } - - @Override - public Predicate visitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { - BooleanBuilder builder = new BooleanBuilder(); - ctx.userConditionGroup().forEach(userConditionGroupContext -> builder.and(visit(userConditionGroupContext))); - return builder; - } - - @Override - public Predicate visitUserConditionGroup(QueryLangParser.UserConditionGroupContext ctx) { - if (ctx.userCondition() != null) { - return visit(ctx.userCondition()); - } - return ( visit(ctx.userConditions()) ); - } - - @Override - public Predicate visitUserCondition(QueryLangParser.UserConditionContext ctx) { - if (ctx.NOT() != null) { - return visit(ctx.userComparisons()).not(); - } - return visit(ctx.userComparisons()); - } - - @Override - public Predicate visitIdComparison(QueryLangParser.IdComparisonContext ctx) { - StringPath stringPath; - switch (type) { - case PROCESS: - stringPath = QPetriNet.petriNet.stringId; - break; - case CASE: - stringPath = QCase.case$.stringId; - break; - case TASK: - stringPath = QTask.task.stringId; - break; - case USER: - stringPath = QUser.user.stringId; - break; - default: - throw new IllegalArgumentException("Search by id is not available for type " + type.name()); - } - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { - StringPath stringPath; - switch (type) { - case PROCESS: - stringPath = QPetriNet.petriNet.title.defaultValue; - break; - case CASE: - stringPath = QCase.case$.title; - break; - case TASK: - stringPath = QTask.task.title.defaultValue; - break; - default: - throw new IllegalArgumentException("Search by title is not available for type " + type.name()); - } - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { - StringPath stringPath = QPetriNet.petriNet.identifier; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitVersionComparison(QueryLangParser.VersionComparisonContext ctx) { - String[] versionNumber = ctx.VERSION_NUMBER().getText().split("\\."); - long major = Long.parseLong(versionNumber[0]); - long minor = Long.parseLong(versionNumber[1]); - long patch = Long.parseLong(versionNumber[2]); - - QVersion qVersion = QPetriNet.petriNet.version; - - switch (ctx.op.getType()) { - case QueryLangParser.EQ: - return qVersion.eq(new Version(major, minor, patch)); - case QueryLangParser.GT: - return qVersion.major.gt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); - case QueryLangParser.GTE: - return qVersion.major.gt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))) - .or(qVersion.eq(new Version(major, minor, patch))); - case QueryLangParser.LT: - return qVersion.major.lt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); - case QueryLangParser.LTE: - return qVersion.major.lt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) - .or(qVersion.eq(new Version(major, minor, patch))); - } - throw new UnsupportedOperationException("Operator " + ctx.op.getText() + " is not available for version comparison"); - } - - @Override - public Predicate visitCreationDateComparison(QueryLangParser.CreationDateComparisonContext ctx) { - DateTimePath dateTimePath; - switch (type) { - case PROCESS: - dateTimePath = QPetriNet.petriNet.creationDate; - break; - case CASE: - dateTimePath = QCase.case$.creationDate; - break; - default: - throw new IllegalArgumentException("Search by creation date is not available for type " + type.name()); - } - - return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); - } - - @Override - public Predicate visitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { - StringPath stringPath; - switch (type) { - case CASE: - stringPath = QCase.case$.petriNetId; - break; - case TASK: - stringPath = QTask.task.processId; - break; - default: - throw new IllegalArgumentException("Search by process id is not available for type " + type.name()); - } - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { - StringPath stringPath = QCase.case$.author.id; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { - StringPath stringPath = QTask.task.transitionId; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitStateComparison(QueryLangParser.StateComparisonContext ctx) { - switch (ctx.state.getType()) { - case QueryLangParser.ENABLED: - return QTask.task.state.eq(State.ENABLED); - case QueryLangParser.DISABLED: - return QTask.task.state.eq(State.DISABLED); - } - - throw new IllegalArgumentException("Invalid task state: " + ctx.state.getType()); - } - - @Override - public Predicate visitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { - StringPath stringPath = QTask.task.userId; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { - StringPath stringPath = QTask.task.caseId; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitLastAssignComparison(QueryLangParser.LastAssignComparisonContext ctx) { - DateTimePath dateTimePath = QTask.task.lastAssigned; - - return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); - } - - @Override - public Predicate visitLastFinishComparison(QueryLangParser.LastFinishComparisonContext ctx) { - DateTimePath dateTimePath = QTask.task.lastFinished; - - return evaluateDateOrDateTimeComparison(dateTimePath, ctx.dateComparison(), ctx.dateTimeComparison()); - } - - @Override - public Predicate visitNameComparison(QueryLangParser.NameComparisonContext ctx) { - StringPath stringPath = QUser.user.name; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { - StringPath stringPath = QUser.user.surname; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { - StringPath stringPath = QUser.user.email; - - return evaluateStringComparison(stringPath, ctx.stringComparison()); - } - - @Override - public Predicate visitDataComparison(QueryLangParser.DataComparisonContext ctx) { - throw new UnsupportedOperationException("Case search by data is not available for MongoDB"); - } - - @Override - public Predicate visitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { - throw new UnsupportedOperationException("Case search by place is not available for MongoDB"); - - } - - @Override - public Predicate visitTasksComparison(QueryLangParser.TasksComparisonContext ctx) { - throw new UnsupportedOperationException("Case search by tasks is not available for MongoDB"); - } - - private static Predicate evaluateStringComparison(StringPath stringPath, QueryLangParser.StringComparisonContext ctx) { - String string = ctx.STRING().getText(); - switch (ctx.op.getType()) { - case QueryLangParser.EQ: - return stringPath.eq(string); - case QueryLangParser.CONTAINS: - return stringPath.contains(string); - } - - throw new UnsupportedOperationException("Operator " + ctx.op.getText() + " is not available for string comparison"); - } - - private static Predicate evaluateDateOrDateTimeComparison(DateTimePath dateTimePath, QueryLangParser.DateComparisonContext dateComparisonContext, QueryLangParser.DateTimeComparisonContext dateTimeComparisonContext) { - if (dateComparisonContext != null) { - return getDateTimePredicate(dateTimePath, dateComparisonContext.op, dateComparisonContext.DATE().getText()); - } else if (dateTimeComparisonContext != null) { - return getDateTimePredicate(dateTimePath, dateTimeComparisonContext.op, dateTimeComparisonContext.DATETIME().getText()); - } - throw new IllegalArgumentException("Date or date time comparison expected"); - } - - private static Predicate getDateTimePredicate(DateTimePath dateTimePath, Token op, String input) { - LocalDateTime localDateTime = getLocalDateTime(input); - - switch (op.getType()) { - case QueryLangParser.EQ: - return dateTimePath.eq(localDateTime); - case QueryLangParser.LT: - return dateTimePath.lt(localDateTime); - case QueryLangParser.LTE: - return dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); - case QueryLangParser.GT: - return dateTimePath.gt(localDateTime); - case QueryLangParser.GTE: - return dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); - } - - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); - } - - private static LocalDateTime getLocalDateTime(String input) { - try { - return LocalDateTime.parse(input, DateTimeFormatter.ofPattern(SearchService.DATE_TIME_PATTERN)); - } catch (DateTimeParseException ignored) { - try { - return LocalDate.parse(input, DateTimeFormatter.ofPattern(SearchService.DATE_PATTERN)).atStartOfDay(); - } catch (DateTimeParseException e) { - throw new IllegalArgumentException("Invalid date/datetime format"); - } - } - } -} diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index 5d7c47680ae..978fc6e5ab2 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -3,10 +3,17 @@ import com.netgrif.application.engine.antlr4.QueryLangLexer; import com.netgrif.application.engine.antlr4.QueryLangParser; import com.netgrif.application.engine.auth.domain.repositories.UserRepository; +import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; +import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.utils.FullPageRequest; +import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; import com.netgrif.application.engine.workflow.domain.repositories.TaskRepository; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.querydsl.core.types.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,23 +21,32 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; +import java.util.List; + @Slf4j @Service @RequiredArgsConstructor -public class SearchService { +public class SearchService implements ISearchService { + + private final PetriNetRepository petriNetRepository; private final IElasticCaseService elasticCaseService; private final CaseRepository caseRepository; + private final IWorkflowService workflowService; + private final TaskRepository taskRepository; - private final PetriNetRepository petriNetRepository; + private final ITaskService taskService; private final UserRepository userRepository; + private final IUserService userService; + private static ParseTree getParseTree(String input) { if (input == null || input.isEmpty()) { return null; @@ -42,42 +58,102 @@ private static ParseTree getParseTree(String input) { return parser.query(); } + private Long countCasesElastic(String elasticQuery) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.count( + List.of(caseSearchRequest), + userService.getLoggedOrSystem().transformToLoggedUser(), + LocaleContextHolder.getLocale(), + false + ); + } + + private List findCasesElastic(String elasticQuery) { + CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); + caseSearchRequest.query = elasticQuery; + return elasticCaseService.search( + List.of(caseSearchRequest), + userService.getLoggedOrSystem().transformToLoggedUser(), + new FullPageRequest(), LocaleContextHolder.getLocale(), + false + ).getContent(); + } + + @Override public Object search(String input) { - ParseTree tree = getParseTree(input); - // todo NAE-1997: implement actual search + ParseTree root = getParseTree(input); + ParseTreeWalker walker = new ParseTreeWalker(); + QueryLangEvaluator evaluator = new QueryLangEvaluator(); + + try { + walker.walk(evaluator, root); + + Predicate predicate = evaluator.getMongoQuery(root); + String elasticQuery = evaluator.getElasticQuery(root); + switch (evaluator.getType()) { + case PROCESS: + if (evaluator.getMultiple()) { + return petriNetRepository.findAll(predicate, new FullPageRequest()).getContent(); + } + return petriNetRepository.findOne(predicate); + case CASE: + if (predicate != null) { + if (evaluator.getMultiple()) { + return workflowService.searchAll(predicate).getContent(); + } + return workflowService.searchOne(predicate); + } + + List cases = findCasesElastic(elasticQuery); + return evaluator.getMultiple() ? cases : cases.get(0); + case TASK: + if (evaluator.getMultiple()) { + return taskService.searchAll(predicate).getContent(); + } + return taskService.searchOne(predicate); + case USER: + if (evaluator.getMultiple()) { + return userRepository.findAll(predicate, new FullPageRequest()).getContent(); + } + return userRepository.findOne(predicate).orElse(null); + } + } catch (IllegalArgumentException e) { + log.error(e.getMessage()); + } + return null; } - public long count(String input) { - ParseTree tree = getParseTree(input); - // todo NAE-1997: implement actual count + @Override + public Long count(String input) { + ParseTree root = getParseTree(input); ParseTreeWalker walker = new ParseTreeWalker(); - QueryLangEvaluator evaluator2 = new QueryLangEvaluator(); + QueryLangEvaluator evaluator = new QueryLangEvaluator(); try { - walker.walk(evaluator2, tree); + walker.walk(evaluator, root); - Predicate predicate = evaluator2.getMongoQuery(tree); - String elasticQuery = evaluator2.getElasticQuery(tree); - switch (evaluator2.getType()) { + Predicate predicate = evaluator.getMongoQuery(root); + String elasticQuery = evaluator.getElasticQuery(root); + switch (evaluator.getType()) { case PROCESS: return petriNetRepository.count(predicate); case CASE: - return caseRepository.count(predicate); + if (predicate != null) { + return caseRepository.count(predicate); + } + return countCasesElastic(elasticQuery); case TASK: return taskRepository.count(predicate); case USER: return userRepository.count(predicate); } - - - } catch (UnsupportedOperationException | IllegalArgumentException e) { - // todo NAE-1997: count with elastic? + } catch (IllegalArgumentException e) { log.error(e.getMessage()); } - - return 0; + return null; } } diff --git a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java new file mode 100644 index 00000000000..93135262bf1 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java @@ -0,0 +1,8 @@ +package com.netgrif.application.engine.search.interfaces; + +public interface ISearchService { + + Object search(String query); + + Long count(String query); +} From 2ec90509fcdd3504a592271fad66bb227bdcaacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Thu, 22 Aug 2024 17:24:11 +0200 Subject: [PATCH 10/27] [NAE-1997] Query language - update grammar - add error handling when parsing - refactor search service and utils - add places and tasks to elastic case, add options to elastic map field - remove unnecessary antlr4 config option visitor --- pom.xml | 1 - .../application/engine/antlr4/QueryLang.g4 | 37 ++++-- .../engine/elastic/domain/ElasticCase.java | 23 ++-- .../engine/elastic/domain/MapField.java | 14 +- .../engine/elastic/domain/Place.java | 21 +++ .../engine/elastic/domain/Task.java | 27 ++++ .../EnumerationMapFieldTransformer.java | 2 +- .../MultichoiceMapFieldTransformer.java | 2 +- .../engine/search/QueryLangErrorListener.java | 15 +++ .../engine/search/QueryLangEvaluator.java | 120 +++++++++++------ .../engine/search/SearchService.java | 125 +++++++----------- .../engine/search/SearchUtils.java | 26 +++- 12 files changed, 259 insertions(+), 154 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/elastic/domain/Place.java create mode 100644 src/main/java/com/netgrif/application/engine/elastic/domain/Task.java create mode 100644 src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java diff --git a/pom.xml b/pom.xml index 6add8ca2c01..5972285d55b 100644 --- a/pom.xml +++ b/pom.xml @@ -795,7 +795,6 @@ src/main/java ${project.build.directory}/generated-sources/java/ - true diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 index 482a87455a0..797f05934ed 100644 --- a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 @@ -45,12 +45,15 @@ processComparisons: idComparison caseComparisons: idComparison | processIdComparison + | processIdentifierComparison | titleComparison | creationDateComparison | authorComparison | placesComparison - | tasksComparison - | dataComparison + | tasksStateComparison + | tasksUserIdComparison + | dataValueComparison + | dataOptionsComparison ; taskComparisons: idComparison @@ -79,9 +82,10 @@ creationDateComparison: CREATION_DATE SPACE dateComparison # cdDate | CREATION_DATE SPACE dateTimeComparison # cdDateTime ; // todo NAE-1997: date/datetime? processIdComparison: PROCESS_ID SPACE stringComparison ; +processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison ; authorComparison: AUTHOR SPACE stringComparison ; transitionIdComparison: TRANSITION_ID SPACE stringComparison ; -stateComparison: STATE SPACE EQ state=(ENABLED | DISABLED) ; +stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; userIdComparison: USER_ID SPACE stringComparison ; caseIdComparison: CASE_ID SPACE stringComparison ; lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDate @@ -93,14 +97,16 @@ lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDate nameComparison: NAME SPACE stringComparison ; surnameComparison: SURNAME SPACE stringComparison ; emailComparison: EMAIL SPACE stringComparison ; -dataComparison: data SPACE stringComparison # dataString - | data SPACE numberComparison # dataNumber - | data SPACE dateComparison # dataDate - | data SPACE dateTimeComparison # dataDatetime - | data SPACE booleanComparison # dataBoolean +dataValueComparison: dataValue SPACE stringComparison # dataString + | dataValue SPACE numberComparison # dataNumber + | dataValue SPACE dateComparison # dataDate + | dataValue SPACE dateTimeComparison # dataDatetime + | dataValue SPACE booleanComparison # dataBoolean ; +dataOptionsComparison: dataOptions stringComparison ; placesComparison: places SPACE numberComparison ; -tasksComparison: tasks SPACE stringComparison ; +tasksStateComparison: tasksState SPACE op=EQ SPACE state=(ENABLED | DISABLED) ; +tasksUserIdComparison: tasksUserId SPACE stringComparison ; // basic comparisons stringComparison: op=(EQ | CONTAINS) SPACE STRING ; @@ -110,9 +116,11 @@ dateTimeComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; booleanComparison: op=EQ SPACE BOOLEAN ; // special attribute rules -data: DATA '.' fieldId=JAVA_ID '.' property=(VALUE | OPTIONS) ; -places: PLACES '.' placeId=JAVA_ID '.' MARKING ; // todo NAE-1997: places structure in elastic -tasks: TASKS '.' taskId=JAVA_ID '.' property=(STATE | USER_ID) ; // todo NAE-1997: tasks structure in elastic, state comparison? +dataValue: DATA '.' fieldId=JAVA_ID '.'VALUE ; +dataOptions: DATA '.' fieldId=JAVA_ID '.' OPTIONS ; +places: PLACES '.' placeId=JAVA_ID '.' MARKING ; +tasksState: TASKS '.' taskId=JAVA_ID '.' STATE ; +tasksUserId: TASKS '.' taskId=JAVA_ID '.' USER_ID ; // operators AND: A N D | '&' ; @@ -142,6 +150,7 @@ IDENTIFIER: I D E N T I F I E R ; VERSION: V E R S I O N ; CREATION_DATE: C R E A T I O N D A T E ; PROCESS_ID: P R O C E S S I D ; +PROCESS_IDENTIFIER: P R O C E S S I D E N T I F I E R ; AUTHOR: A U T H O R ; PLACES: P L A C E S ; TRANSITION_ID: T R A N S I T I O N I D ; @@ -165,8 +174,8 @@ DISABLED: D I S A B L E D ; LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; NUMBER: DIGIT+ ('.' DIGIT+)? ; -DATETIME: DATE 'T' DIGIT DIGIT ':' DIGIT DIGIT ':' DIGIT DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00 todo NAE-1997 better recognition -DATE: DIGIT DIGIT DIGIT DIGIT '-' DIGIT DIGIT '-' DIGIT DIGIT ; // 2020-03-03, todo NAE-1997 better recognition +DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00 +DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 BOOLEAN: T R U E | F A L S E ; VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java index ebe113ea85c..49480de8bae 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/ElasticCase.java @@ -77,11 +77,9 @@ public class ElasticCase { private Map dataSet; - @Field(type = Keyword) - private Set taskIds; + private Map tasks; - @Field(type = Keyword) - private Set taskMongoIds; + private Map places; @Field(type = Keyword) private Set enabledRoles; @@ -126,8 +124,6 @@ public ElasticCase(Case useCase) { author = useCase.getAuthor().getId(); authorName = useCase.getAuthor().getFullName(); authorEmail = useCase.getAuthor().getEmail(); - taskIds = useCase.getTasks().keySet(); - taskMongoIds = useCase.getTasks().values().stream().map(TaskPair::getTaskStringId).collect(Collectors.toSet()); enabledRoles = new HashSet<>(useCase.getEnabledRoles()); viewRoles = new HashSet<>(useCase.getViewRoles()); viewUserRefs = new HashSet<>(useCase.getViewUserRefs()); @@ -137,6 +133,17 @@ public ElasticCase(Case useCase) { tags = new HashMap<>(useCase.getTags()); dataSet = new HashMap<>(); + + tasks = useCase.getTasks().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new Task(entry.getValue().getTaskStringId(), entry.getValue().getState(), entry.getValue().getUserId()) + )); + places = useCase.getActivePlaces().entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new Place(entry.getValue()) + )); } public void update(ElasticCase useCase) { @@ -146,8 +153,6 @@ public void update(ElasticCase useCase) { uriNodeId = useCase.getUriNodeId(); } title = useCase.getTitle(); - taskIds = useCase.getTaskIds(); - taskMongoIds = useCase.getTaskMongoIds(); enabledRoles = useCase.getEnabledRoles(); viewRoles = useCase.getViewRoles(); viewUserRefs = useCase.getViewUserRefs(); @@ -157,5 +162,7 @@ public void update(ElasticCase useCase) { tags = useCase.getTags(); dataSet = useCase.getDataSet(); + tasks = useCase.getTasks(); + places = useCase.getPlaces(); } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java b/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java index 0e3d22a4317..e75fb41cd3e 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/MapField.java @@ -1,14 +1,12 @@ package com.netgrif.application.engine.elastic.domain; +import com.netgrif.application.engine.petrinet.domain.I18nString; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.springframework.data.elasticsearch.annotations.Field; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import static org.springframework.data.elasticsearch.annotations.FieldType.Keyword; @@ -20,17 +18,21 @@ public class MapField extends TextField { @Field(type = Keyword) public List keyValue = new ArrayList<>(); - public MapField(String key, List values) { + public List options = new ArrayList<>(); + + public MapField(String key, List values, Map options) { super(values); this.keyValue.add(key); + this.options.addAll(options.keySet()); } - public MapField(Map> valuePairs) { + public MapField(Map> valuePairs, Map options) { super(); valuePairs.forEach((key, value) -> { this.keyValue.add(key); this.textValue.addAll(value); this.fulltextValue.addAll(value); }); + this.options.addAll(options.keySet()); } } diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java b/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java new file mode 100644 index 00000000000..b6c14908bd1 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/Place.java @@ -0,0 +1,21 @@ +package com.netgrif.application.engine.elastic.domain; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.data.elasticsearch.annotations.Field; + +import static org.springframework.data.elasticsearch.annotations.FieldType.Integer; +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +@AllArgsConstructor +public class Place { + + @Field(type = Integer) + public Integer marking; + +} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java b/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java new file mode 100644 index 00000000000..fecd15381ed --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/elastic/domain/Task.java @@ -0,0 +1,27 @@ +package com.netgrif.application.engine.elastic.domain; + +import com.netgrif.application.engine.workflow.domain.State; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.springframework.data.elasticsearch.annotations.Field; + +import static org.springframework.data.elasticsearch.annotations.FieldType.Text; + +@Data +@NoArgsConstructor +@EqualsAndHashCode +@AllArgsConstructor +public class Task { + + @Field(type = Text) + public String stringId; + + @Field(type = Text) + public State state; + + @Field(type = Text) + public String userId; + +} \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java b/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java index d472da66020..393cb1f0709 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/transform/EnumerationMapFieldTransformer.java @@ -18,7 +18,7 @@ public MapField transform(EnumerationMapField caseField, EnumerationMapField pet DataFieldValue selectedKey = caseField.getValue(); String value = selectedKey != null ? selectedKey.getValue() : null; I18nString selectedValue = options.get(value) != null ? options.get(value) : new I18nString(); - return new MapField(value, selectedValue.collectTranslations()); + return new MapField(value, selectedValue.collectTranslations(), caseField.getOptions()); } @Override diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java b/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java index 80c4ea2bd6c..59948dc3123 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/transform/MultichoiceMapFieldTransformer.java @@ -25,7 +25,7 @@ public MapField transform(MultichoiceMapField caseField, MultichoiceMapField pet I18nString selectedValue = options.get(value) != null ? options.get(value) : new I18nString(); fieldValues.put(value, selectedValue.collectTranslations()); } - return new MapField(fieldValues); + return new MapField(fieldValues, options); } @Override diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java new file mode 100644 index 00000000000..55ef0b585a1 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java @@ -0,0 +1,15 @@ +package com.netgrif.application.engine.search; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +public class QueryLangErrorListener extends BaseErrorListener { + + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) + throws ParseCancellationException { + throw new IllegalArgumentException("line " + line + ":" + charPositionInLine + " " + msg); + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index d52a9edf7bc..9ee50355a83 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -21,6 +21,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static com.netgrif.application.engine.search.SearchUtils.*; @@ -34,6 +35,10 @@ public class QueryLangEvaluator extends QueryLangBaseListener { private QueryType type; @Getter private Boolean multiple; + @Getter + private Predicate fullMongoQuery; + @Getter + private String fullElasticQuery; public void setElasticQuery(ParseTree node, String query) { elasticQuery.put(node, query); @@ -57,31 +62,39 @@ private void processBasicExpression(ParseTree child, ParseTree current) { } private void processOrExpression(List children, ParseTree current) { - List predicates = children.stream().map(this::getMongoQuery).collect(Collectors.toList()); - String elasticQuery = children.stream().map(this::getElasticQuery).collect(Collectors.joining(" OR ")); + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" OR ")); - if (predicates.contains(null)) { - setMongoQuery(current, null); - } else { + if (!predicates.isEmpty()) { BooleanBuilder predicate = new BooleanBuilder(); predicates.forEach(predicate::or); setMongoQuery(current, predicate); } - setElasticQuery(current, elasticQuery); + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); } private void processAndExpression(List children, ParseTree current) { - List predicates = children.stream().map(this::getMongoQuery).collect(Collectors.toList()); - String elasticQuery = children.stream().map(this::getElasticQuery).collect(Collectors.joining(" AND ")); + List predicates = children.stream() + .map(this::getMongoQuery) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + String elasticQuery = children.stream() + .map(this::getElasticQuery) + .filter(Objects::nonNull) + .collect(Collectors.joining(" AND ")); - if (predicates.contains(null)) { - setMongoQuery(current, null); - } else { + if (!predicates.isEmpty()) { BooleanBuilder predicate = new BooleanBuilder(); predicates.forEach(predicate::and); setMongoQuery(current, predicate); } - setElasticQuery(current, elasticQuery); + setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); } private void processConditionGroup(ParseTree child, ParseTree current) { @@ -89,7 +102,7 @@ private void processConditionGroup(ParseTree child, ParseTree current) { String elasticQuery = getElasticQuery(child); setMongoQuery(current, (predicate)); - setElasticQuery(current, "(" + elasticQuery + ")"); + setElasticQuery(current, elasticQuery != null ? "(" + elasticQuery + ")" : null); } private void processCondition(ParseTree child, ParseTree current, Boolean not) { @@ -98,7 +111,7 @@ private void processCondition(ParseTree child, ParseTree current, Boolean not) { if (not) { predicate = predicate != null ? predicate.not() : null; - elasticQuery = "NOT " + elasticQuery; + elasticQuery = elasticQuery != null ? "NOT " + elasticQuery : null; } setMongoQuery(current, predicate); @@ -114,6 +127,8 @@ public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { @Override public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { processBasicExpression(ctx.processConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); } @Override @@ -125,6 +140,8 @@ public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { @Override public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { processBasicExpression(ctx.caseConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); } @Override @@ -136,6 +153,8 @@ public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { @Override public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { processBasicExpression(ctx.taskConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); } @Override @@ -147,6 +166,8 @@ public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { @Override public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { processBasicExpression(ctx.userConditions(), ctx); + fullMongoQuery = getMongoQuery(ctx); + fullElasticQuery = getElasticQuery(ctx); } @Override @@ -432,6 +453,16 @@ public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext c setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); } + @Override + public void exitProcessIdentifierComparison(QueryLangParser.ProcessIdentifierComparisonContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + Token op = ctx.stringComparison().op; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setElasticQuery(ctx, buildElasticQuery("processIdentifier", op, string)); + } + @Override public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { StringPath stringPath = QCase.case$.author.id; @@ -543,8 +574,8 @@ public void exitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { } @Override - public void exitDataString(QueryLangParser.DataStringContext ctx) { // todo NAE-1997: options comparison grammar update?? - String fieldId = ctx.data().fieldId.getText(); + public void exitDataString(QueryLangParser.DataStringContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); String string = getStringValue(ctx.stringComparison().STRING().getText()); @@ -555,11 +586,7 @@ public void exitDataString(QueryLangParser.DataStringContext ctx) { // todo NAE- @Override public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { - if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { - throw new IllegalArgumentException("Search by number value is not applicable for options."); - } - - String fieldId = ctx.data().fieldId.getText(); + String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); String number = ctx.numberComparison().NUMBER().getText(); @@ -570,11 +597,7 @@ public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { @Override public void exitDataDate(QueryLangParser.DataDateContext ctx) { - if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { - throw new IllegalArgumentException("Search by date value is not applicable for options."); - } - - String fieldId = ctx.data().fieldId.getText(); + String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.dateComparison().op; checkOp(ComparisonType.DATE, op); LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); @@ -585,11 +608,7 @@ public void exitDataDate(QueryLangParser.DataDateContext ctx) { @Override public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { - if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { - throw new IllegalArgumentException("Search by date time value is not applicable for options."); - } - - String fieldId = ctx.data().fieldId.getText(); + String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.dateTimeComparison().op; checkOp(ComparisonType.DATETIME, op); LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); @@ -600,17 +619,24 @@ public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { @Override public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { - if (ctx.data().property.getType() == QueryLangParser.OPTIONS) { - throw new IllegalArgumentException("Search by boolean value is not applicable for options."); - } - - String fieldId = ctx.data().fieldId.getText(); + String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.booleanComparison().op; checkOp(ComparisonType.BOOLEAN, op); String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, Timestamp.valueOf(booleanValue).toString())); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, booleanValue)); + } + + @Override + public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + Token op = ctx.stringComparison().op; + checkOp(ComparisonType.BOOLEAN, op); + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op, string)); } @Override @@ -621,18 +647,28 @@ public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { String numberValue = ctx.numberComparison().NUMBER().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue)); // todo NAE-1997: update places structure in elastic/another approach? + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue)); + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + String taskId = ctx.tasksState().taskId.getText(); + Token op = ctx.op; + checkOp(ComparisonType.STRING, op); + State state = ctx.state.getType() == QueryLangParser.ENABLED ? State.ENABLED : State.DISABLED; + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op, state.toString())); } @Override - public void exitTasksComparison(QueryLangParser.TasksComparisonContext ctx) { - String taskId = ctx.tasks().taskId.getText(); - String property = ctx.tasks().property.getType() == QueryLangParser.STATE ? ".state" : "userId"; + public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + property, op, string)); // todo NAE-1997: update tasks structure in elastic/another approach? + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op, string)); } } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index 978fc6e5ab2..cb7f9190fd0 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -1,7 +1,5 @@ package com.netgrif.application.engine.search; -import com.netgrif.application.engine.antlr4.QueryLangLexer; -import com.netgrif.application.engine.antlr4.QueryLangParser; import com.netgrif.application.engine.auth.domain.repositories.UserRepository; import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; @@ -17,15 +15,13 @@ import com.querydsl.core.types.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.antlr.v4.runtime.CharStreams; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.tree.ParseTree; -import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.stereotype.Service; import java.util.List; +import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; + @Slf4j @Service @RequiredArgsConstructor @@ -47,17 +43,6 @@ public class SearchService implements ISearchService { private final IUserService userService; - private static ParseTree getParseTree(String input) { - if (input == null || input.isEmpty()) { - return null; - } - - QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); - CommonTokenStream tokens = new CommonTokenStream(lexer); - QueryLangParser parser = new QueryLangParser(tokens); - return parser.query(); - } - private Long countCasesElastic(String elasticQuery) { CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); caseSearchRequest.query = elasticQuery; @@ -82,77 +67,59 @@ private List findCasesElastic(String elasticQuery) { @Override public Object search(String input) { - ParseTree root = getParseTree(input); - ParseTreeWalker walker = new ParseTreeWalker(); - QueryLangEvaluator evaluator = new QueryLangEvaluator(); - - try { - walker.walk(evaluator, root); - - Predicate predicate = evaluator.getMongoQuery(root); - String elasticQuery = evaluator.getElasticQuery(root); - switch (evaluator.getType()) { - case PROCESS: - if (evaluator.getMultiple()) { - return petriNetRepository.findAll(predicate, new FullPageRequest()).getContent(); - } - return petriNetRepository.findOne(predicate); - case CASE: - if (predicate != null) { - if (evaluator.getMultiple()) { - return workflowService.searchAll(predicate).getContent(); - } - return workflowService.searchOne(predicate); - } - - List cases = findCasesElastic(elasticQuery); - return evaluator.getMultiple() ? cases : cases.get(0); - case TASK: - if (evaluator.getMultiple()) { - return taskService.searchAll(predicate).getContent(); - } - return taskService.searchOne(predicate); - case USER: + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + + switch (evaluator.getType()) { + case PROCESS: + if (evaluator.getMultiple()) { + return petriNetRepository.findAll(predicate, new FullPageRequest()).getContent(); + } + return petriNetRepository.findOne(predicate); + case CASE: + if (predicate != null) { if (evaluator.getMultiple()) { - return userRepository.findAll(predicate, new FullPageRequest()).getContent(); + return workflowService.searchAll(predicate).getContent(); } - return userRepository.findOne(predicate).orElse(null); - } - } catch (IllegalArgumentException e) { - log.error(e.getMessage()); + return workflowService.searchOne(predicate); + } + + List cases = findCasesElastic(elasticQuery); + return evaluator.getMultiple() ? cases : cases.get(0); + case TASK: + if (evaluator.getMultiple()) { + return taskService.searchAll(predicate).getContent(); + } + return taskService.searchOne(predicate); + case USER: + if (evaluator.getMultiple()) { + return userRepository.findAll(predicate, new FullPageRequest()).getContent(); + } + return userRepository.findOne(predicate).orElse(null); } - return null; } @Override public Long count(String input) { - ParseTree root = getParseTree(input); - ParseTreeWalker walker = new ParseTreeWalker(); - QueryLangEvaluator evaluator = new QueryLangEvaluator(); - - try { - walker.walk(evaluator, root); - - Predicate predicate = evaluator.getMongoQuery(root); - String elasticQuery = evaluator.getElasticQuery(root); - switch (evaluator.getType()) { - case PROCESS: - return petriNetRepository.count(predicate); - case CASE: - if (predicate != null) { - return caseRepository.count(predicate); - } - return countCasesElastic(elasticQuery); - case TASK: - return taskRepository.count(predicate); - case USER: - return userRepository.count(predicate); - } - } catch (IllegalArgumentException e) { - log.error(e.getMessage()); + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + + switch (evaluator.getType()) { + case PROCESS: + return petriNetRepository.count(predicate); + case CASE: + if (predicate != null) { + return caseRepository.count(predicate); + } + return countCasesElastic(elasticQuery); + case TASK: + return taskRepository.count(predicate); + case USER: + return userRepository.count(predicate); } - return null; } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java index d7b9c7657f4..36884471d8f 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java @@ -1,5 +1,6 @@ package com.netgrif.application.engine.search; +import com.netgrif.application.engine.antlr4.QueryLangLexer; import com.netgrif.application.engine.antlr4.QueryLangParser; import com.netgrif.application.engine.petrinet.domain.QPetriNet; import com.netgrif.application.engine.petrinet.domain.version.QVersion; @@ -8,13 +9,15 @@ import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ParseTreeWalker; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; -import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Map; @@ -42,7 +45,7 @@ public static String toDateTimeString(LocalDate localDate) { } public static String toDateTimeString(LocalDateTime localDateTime) { - return localDateTime.truncatedTo(ChronoUnit.SECONDS).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } public static LocalDateTime toDateTime(String dateTimeString) { @@ -69,6 +72,25 @@ public static LocalDate toDate(String dateString) { } } + public static QueryLangEvaluator evaluateQuery(String input) { + if (input == null || input.isEmpty()) { + throw new IllegalArgumentException("Query cannot be empty."); + } + + QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + QueryLangParser parser = new QueryLangParser(tokens); + parser.removeErrorListeners(); + parser.addErrorListener(new QueryLangErrorListener()); + + ParseTreeWalker walker = new ParseTreeWalker(); + QueryLangEvaluator evaluator = new QueryLangEvaluator(); + + walker.walk(evaluator, parser.query()); + + return evaluator; + } + public static String getStringValue(String queryLangString) { return queryLangString.replace("'", ""); } From 54f71579d16920fd9f7c27bb5ffdd5d7be6a8088 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= Date: Mon, 2 Sep 2024 11:59:28 +0200 Subject: [PATCH 11/27] [NAE-1997] Query language - add tests --- .../engine/search/QueryLangTest.java | 99 ++++ .../engine/search/SearchCaseTest.java | 525 ++++++++++++++++++ .../engine/search/SearchProcessTest.java | 205 +++++++ .../engine/search/SearchUserTest.java | 118 ++++ .../petriNets/search/search_test.xml | 207 +++++++ .../petriNets/search/search_test2.xml | 207 +++++++ 6 files changed, 1361 insertions(+) create mode 100644 src/test/java/com/netgrif/application/engine/search/QueryLangTest.java create mode 100644 src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java create mode 100644 src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java create mode 100644 src/test/java/com/netgrif/application/engine/search/SearchUserTest.java create mode 100644 src/test/resources/petriNets/search/search_test.xml create mode 100644 src/test/resources/petriNets/search/search_test2.xml diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java new file mode 100644 index 00000000000..dba0a547a05 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -0,0 +1,99 @@ +package com.netgrif.application.engine.search; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class QueryLangTest { + + // todo NAE-1997:: simple queries logical predicate comparison + // todo NAE-1997:: complex queries logical predicate comparison + + // todo NAE-1997:: all attributes success + // todo NAE-1997:: all comparison type fail + + @Test + public void testProcessQueriesFail() { + // using case, task, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: author eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: transitionId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: lastFinish eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("process: email eq 'test'")); + } + + @Test + public void testCaseQueriesFail() { + // using process, task, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: transitionId eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: lastFinish eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: email eq 'test'")); + } + + @Test + public void testTaskQueriesFail() { + // using process, case, user attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: name eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: surname eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: email eq 'test'")); + } + + @Test + public void testUserQueriesFail() { + // using process, case, task attributes + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processIdentifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: author eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: places.p1.marking eq 1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: tasks.t1.userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.value eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: data.field1.options contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: transitionId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: state eq enabled")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: userId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: caseId eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastAssign eq 2020-03-03")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastFinish eq 2020-03-03")); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java new file mode 100644 index 00000000000..ce725089b64 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -0,0 +1,525 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.petrinet.domain.dataset.*; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchCaseTest { + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + private Map auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private PetriNet importPetriNet(String fileName) { + PetriNet testNet = importHelper.createNet(fileName).orElse(null); + assert testNet != null; + return testNet; + } + + private IUser createUser(String name, String surname, String email, String authority) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get(authority)}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return importHelper.createUser(user, authorities, processRoles); + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case caze = importHelper.createCase("Search Test", net); + + String query = String.format("case: id eq '%s'", caze.getStringId()); + + long count = searchService.count(query); + assert count == 1; + + Object foundCase = searchService.search(query); + + assert foundCase instanceof Case; + assert foundCase.equals(caze); + } + + @Test + public void testSearchByProcessId() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net2); + + String query = String.format("case: processId eq '%s'", net.getStringId()); + String queryOther = String.format("case: processId eq '%s'", net2.getStringId()); + String queryMore = String.format("cases: processId eq '%s'", net.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByProcessIdentifier() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net2); + + String query = String.format("case: processIdentifier eq '%s'", net.getIdentifier()); + String queryOther = String.format("case: processIdentifier eq '%s'", net2.getIdentifier()); + String queryMore = String.format("cases: processIdentifier eq '%s'", net.getIdentifier()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object processes = searchService.search(queryMore); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + String query = String.format("case: title eq '%s'", case1.getTitle()); + String queryOther = String.format("case: title eq '%s'", case3.getTitle()); + String queryMore = String.format("cases: title eq '%s'", case1.getTitle()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByCreationDate() { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test", net); + + String queryEq = String.format("process: creationDate eq '%s'", toDateTimeString(case1.getCreationDate())); + String queryLt = String.format("processes: creationDate lt '%s'", toDateTimeString(case3.getCreationDate())); + String queryLte = String.format("processes: creationDate lte '%s'", toDateTimeString(case3.getCreationDate())); + String queryGt = String.format("processes: creationDate gt '%s'", toDateTimeString(case1.getCreationDate())); + String queryGte = String.format("processes: creationDate gte '%s'", toDateTimeString(case1.getCreationDate())); + + long count = searchService.count(queryEq); + assert count == 1; + + Object foundCase = searchService.search(queryEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + count = searchService.count(queryLt); + assert count == 2; + + Object cases = searchService.search(queryLt); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + + count = searchService.count(queryLte); + assert count == 2; + + cases = searchService.search(queryLte); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2, case3)); + + count = searchService.count(queryGt); + assert count == 2; + + cases = searchService.search(queryGt); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case2, case3)); + + count = searchService.count(queryGte); + assert count == 2; + + cases = searchService.search(queryGte); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2, case3)); + } + + @Test + public void testSearchByAuthor() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); + + Case case1 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); + Case case2 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); + Case case3 = importHelper.createCase("Search Test2", net, user2.transformToLoggedUser()); + + String query = String.format("case: author eq '%s'", user1.getStringId()); + String queryOther = String.format("case: author eq '%s'", user2.getStringId()); + String queryMore = String.format("cases: author eq '%s'", user1.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByPlaces() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String query = String.format("case: places.p2.marking eq %s", 1); + String queryOther = String.format("case: places.p1.marking eq %s", 1); + String queryMore = String.format("cases: places.p2.marking eq %s", 1); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByTaskState() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String query = String.format("case: tasks.t1.state eq %s", "disabled"); + String queryOther = String.format("case: tasks.t1.state eq %s", "enabled"); + String queryMore = String.format("cases: tasks.t1.state eq %s", "disabled"); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + @Test + public void testSearchByTaskUserId() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); + + String query = String.format("case: tasks.t1.userId eq '%s'", user1.getStringId()); + String queryOther = String.format("case: tasks.t1.userId eq '%s'", user2.getStringId()); + String queryMore = String.format("cases: tasks.t1.userId eq '%s'", user1.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundCase = searchService.search(query); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryOther); + assert foundCase instanceof Case; + assert foundCase.equals(case3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List) cases).containsAll(List.of(case1, case2)); + } + + // todo: change values + +// BooleanField booleanTrue = new BooleanField(); +// booleanTrue.setRawValue(true); +// BooleanField booleanFalse = new BooleanField(); +// booleanFalse.setRawValue(false); +// TextField textField = new TextField(); +// textField.setRawValue("test"); +// Map options = Map.of("test1", new I18nString("Test1"), "test2", new I18nString("Test2"), "test3", new I18nString("Test3")); +// EnumerationMapField enumerationMapField = new EnumerationMapField(); +// enumerationMapField.setOptions(options); +// enumerationMapField.setRawValue("test1"); +// MultichoiceMapField multichoiceMapField = new MultichoiceMapField(); +// multichoiceMapField.setOptions(options); +// multichoiceMapField.setRawValue(Set.of("test1", "test2")); +// NumberField numberField = new NumberField(); +// numberField.setRawValue(2.0); +// DateField dateField = new DateField(); +// dateField.setRawValue(LocalDate.now()); +// DateTimeField dateTimeField = new DateTimeField(); +// dateTimeField.setRawValue(LocalDateTime.now()); + +// importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); +// importHelper.setTaskData("Test", case1.getStringId(), new DataSet(Map.of( +// "boolean_immediate", booleanTrue, +// "text_immediate", textField, +// "number_immediate", numberField, +// "multichoice_immediate", multichoiceMapField, +// "enumeration_immediate", enumerationMapField, +// "date_immediate", dateField, +// "date_time_immediate", dateTimeField +// ))); +// importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); +// importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); +// importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + @Test + public void testSearchByDataValue() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + + + + String queryTextEq = String.format("case: data.text_immediate.value eq %s", "'test'"); + String queryTextContains = String.format("case: data.text_immediate.value contains %s", "'es'"); + String queryBoolean = String.format("case: data.boolean_immediate.value eq %s", "true"); + String queryEnumerationEq = String.format("case: data.enumeration_map_immediate.value eq %s", "'key2'"); + String queryEnumerationContains = String.format("case: data.enumeration_map_immediate.value contains %s", "'ey2'"); + String queryMultichoiceEq = String.format("case: data.multichoice_map_immediate.value eq %s", "'key2'"); + String queryMultichoiceContains = String.format("case: data.multichoice_map_immediate.value contains %s", "'ey2'"); + String queryNumberEq = String.format("case: data.number_immediate.value eq %s", 54); + String queryNumberLt = String.format("case: data.number_immediate.value lt %s", 55); + String queryNumberLte = String.format("case: data.number_immediate.value lte %s", 55); + String queryNumberGt = String.format("case: data.number_immediate.value gt %s", 53); + String queryNumberGte = String.format("case: data.number_immediate.value gte %s", 53); + String queryDateEq = String.format("case: data.date_immediate.value eq %s", SearchUtils.toDateString(LocalDate.now())); + String queryDateLt = String.format("case: data.date_immediate.value lt %s", SearchUtils.toDateString(LocalDate.now().plusDays(1))); + String queryDateLte = String.format("case: data.date_immediate.value lte %s", SearchUtils.toDateString(LocalDate.now().plusDays(1))); + String queryDateGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); + String queryDateGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); + String queryDateTimeEq = String.format("case: data.date_immediate.value eq %s", SearchUtils.toDateString((LocalDateTime) case1.getDataSet().get("date_immediate").getRawValue())); + String queryDateTimeLt = String.format("case: data.date_immediate.value lt %s", SearchUtils.toDateString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeLte = String.format("case: data.date_immediate.value lte %s", SearchUtils.toDateString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); + String queryDateTimeGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); + + + long count = searchService.count(queryTextEq); + assert count == 1; + + count = searchService.count(queryTextContains); + assert count == 1; + + count = searchService.count(queryBoolean); + assert count == 1; + + count = searchService.count(queryEnumerationEq); + assert count == 1; + + count = searchService.count(queryEnumerationContains); + assert count == 1; + + count = searchService.count(queryMultichoiceEq); + assert count == 1; + + count = searchService.count(queryMultichoiceContains); + assert count == 1; + + count = searchService.count(queryNumberEq); + assert count == 1; + + count = searchService.count(queryNumberLt); + assert count == 1; + + count = searchService.count(queryNumberLte); + assert count == 1; + + count = searchService.count(queryNumberGt); + assert count == 1; + + count = searchService.count(queryNumberGte); + assert count == 1; + + count = searchService.count(queryDateEq); + assert count == 1; + + count = searchService.count(queryDateLt); + assert count == 1; + + count = searchService.count(queryDateLte); + assert count == 1; + + count = searchService.count(queryDateGt); + assert count == 1; + + count = searchService.count(queryDateGte); + assert count == 1; + + count = searchService.count(queryDateTimeEq); + assert count == 1; + + count = searchService.count(queryDateTimeLt); + assert count == 1; + + count = searchService.count(queryDateTimeLte); + assert count == 1; + + count = searchService.count(queryDateTimeGt); + assert count == 1; + + count = searchService.count(queryDateTimeGte); + assert count == 1; + + Object foundCase = searchService.search(queryTextEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryTextContains); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + // todo: other assertions + } + +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java new file mode 100644 index 00000000000..71ed999349b --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -0,0 +1,205 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; + +import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchProcessTest { + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + } + + private PetriNet importPetriNet(String fileName, VersionType versionType) { + PetriNet testNet = importHelper.createNet(fileName, versionType).orElse(null); + assert testNet != null; + return testNet; + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String query = String.format("process: id eq '%s'", net.getStringId()); + + long count = searchService.count(query); + assert count == 1; + + Object process = searchService.search(query); + + assert process instanceof PetriNet; + assert process.equals(net); + } + + @Test + public void testSearchByIdentifier() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String query = String.format("process: identifier eq '%s'", net.getIdentifier()); + String queryMore = String.format("processes: identifier eq '%s'", net.getIdentifier()); + + long count = searchService.count(query); + assert count == 2; + + Object process = searchService.search(query); + assert process instanceof PetriNet; + assert process.equals(net) || process.equals(netNewer); + + Object processes = searchService.search(queryMore); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewer)); + } + + @Test + public void testSearchByVersion() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewerPatch = importPetriNet("search/search_test.xml", VersionType.PATCH); + PetriNet netNewerMinor = importPetriNet("search/search_test.xml", VersionType.MINOR); + PetriNet netNewerMajor = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String queryEq = String.format("process: version eq '%s'", "1.0.0"); + String queryLt = String.format("processes: version lt '%s'", "2.0.0"); + String queryLte = String.format("processes: version lte '%s'", "2.0.0"); + String queryGt = String.format("processes: version gt '%s'", "1.0.0"); + String queryGte = String.format("processes: version gt '%s'", "1.0.0"); + + long count = searchService.count(queryEq); + assert count == 1; + + Object process = searchService.search(queryEq); + assert process instanceof PetriNet; + assert process.equals(net); + + count = searchService.count(queryLt); + assert count == 3; + + Object processes = searchService.search(queryLt); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor)); + assert !((List) processes).contains(netNewerMajor); + + count = searchService.count(queryLte); + assert count == 4; + + processes = searchService.search(queryLte); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + + count = searchService.count(queryGt); + assert count == 3; + + processes = searchService.search(queryGt); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(netNewerPatch, netNewerMinor, netNewerMajor)); + assert !((List) processes).contains(net); + + count = searchService.count(queryGte); + assert count == 4; + + processes = searchService.search(queryGte); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String query = String.format("process: title eq '%s'", net.getTitle().toString()); + String queryMore = String.format("processes: title eq '%s'", net.getTitle().toString()); + + long count = searchService.count(query); + assert count == 2; + + Object process = searchService.search(query); + assert process instanceof PetriNet; + assert process.equals(net) || process.equals(netNewer); + + Object processes = searchService.search(queryMore); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewer)); + } + + @Test + public void testSearchByCreationDate() { + PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + + String queryEq = String.format("process: creationDate eq '%s'", toDateTimeString(net.getCreationDate())); + String queryLt = String.format("processes: creationDate lt '%s'", toDateTimeString(net2.getCreationDate())); + String queryLte = String.format("processes: creationDate lte '%s'", toDateTimeString(net2.getCreationDate())); + String queryGt = String.format("processes: creationDate gt '%s'", toDateTimeString(net.getCreationDate())); + String queryGte = String.format("processes: creationDate gte '%s'", toDateTimeString(net.getCreationDate())); + + long count = searchService.count(queryEq); + assert count == 1; + + Object process = searchService.search(queryEq); + assert process instanceof PetriNet; + assert process.equals(net); + + count = searchService.count(queryLt); + assert count == 2; + + Object processes = searchService.search(queryLt); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewer)); + + count = searchService.count(queryLte); + assert count == 2; + + processes = searchService.search(queryLte); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewer, net2)); + + count = searchService.count(queryGt); + assert count == 2; + + processes = searchService.search(queryGt); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(netNewer, net2)); + + count = searchService.count(queryGte); + assert count == 2; + + processes = searchService.search(queryGte); + assert processes instanceof List; + assert ((List) processes).containsAll(List.of(net, netNewer, net2)); + } + +} diff --git a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java new file mode 100644 index 00000000000..bb3c79e4ac1 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java @@ -0,0 +1,118 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; +import java.util.Map; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchUserTest { + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + private Map auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private IUser createUser(String name, String surname, String email, String authority) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get(authority)}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return importHelper.createUser(user, authorities, processRoles); + } + + @Test + public void testSearchById() { + IUser user1 = createUser("name1", "surname1", "email1", "user"); + IUser user2 = createUser("name2", "surname2", "email2", "admin"); + + String query = String.format("user: id eq '%s'", user1.getStringId()); + + long count = searchService.count(query); + assert count == 1; + + Object foundUser = searchService.search(query); + + assert foundUser instanceof User; + assert foundUser.equals(user1); + } + + @Test + public void testSearchByEmail() { + IUser user1 = createUser("name1", "surname1", "email1", "user"); + IUser user2 = createUser("name2", "surname2", "email2", "admin"); + + String query = String.format("user: email eq '%s'", user1.getEmail()); + + long count = searchService.count(query); + assert count == 1; + + Object foundUser = searchService.search(query); + + assert foundUser instanceof User; + assert foundUser.equals(user1); + } + + @Test + public void testSearchByName() { + IUser user1 = createUser("name1", "surname1", "email1", "user"); + IUser user2 = createUser("name2", "surname2", "email2", "admin"); + IUser user3 = createUser("name1", "surname1", "email3", "user"); + + String query = String.format("users: name eq '%s'", user1.getName()); + + long count = searchService.count(query); + assert count == 2; + + Object foundUsers = searchService.search(query); + + assert foundUsers instanceof List; + assert ((List) foundUsers).containsAll(List.of(user1, user3)); + } + + @Test + public void testSearchBySurname() { + IUser user1 = createUser("name1", "surname1", "email1", "user"); + IUser user2 = createUser("name2", "surname2", "email2", "admin"); + IUser user3 = createUser("name1", "surname1", "email3", "user"); + + String query = String.format("users: surname eq '%s'", user1.getName()); + + long count = searchService.count(query); + assert count == 2; + + Object foundUsers = searchService.search(query); + + assert foundUsers instanceof List; + assert ((List) foundUsers).containsAll(List.of(user1, user3)); + } + +} diff --git a/src/test/resources/petriNets/search/search_test.xml b/src/test/resources/petriNets/search/search_test.xml new file mode 100644 index 00000000000..c4da5e43798 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test.xml @@ -0,0 +1,207 @@ + + search_test + ST1 + Search Test + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>t1</id> + <x>260</x> + <y>100</y> + <label>Test</label> + <dataGroup> + <id>t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>t1_assign</id> + </event> + <event type="finish"> + <id>t1_finish</id> + </event> + <event type="cancel"> + <id>t1_cancel</id> + </event> + <event type="delegate"> + <id>t1_delegate</id> + </event> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file diff --git a/src/test/resources/petriNets/search/search_test2.xml b/src/test/resources/petriNets/search/search_test2.xml new file mode 100644 index 00000000000..f6fe37525c5 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test2.xml @@ -0,0 +1,207 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>search_test2</id> + <initials>ST2</initials> + <title>Search Test 2 + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>t1</id> + <x>260</x> + <y>100</y> + <label>Test</label> + <dataGroup> + <id>t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>t1_assign</id> + </event> + <event type="finish"> + <id>t1_finish</id> + </event> + <event type="cancel"> + <id>t1_cancel</id> + </event> + <event type="delegate"> + <id>t1_delegate</id> + </event> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file From 7dc80e30a8d8e761a8883becd4ef1038452deaca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 29 Jan 2025 16:59:21 +0100 Subject: [PATCH 12/27] [NAE-1997] Query language - update query language grammar (remove comments, add id comparison, add NOT before parenthesis) - update find one feature - add query language generic tests - add task search tests - update case search tests --- .../engine/search/QueryLangEvaluator.java | 51 +- .../engine/search/SearchService.java | 11 +- .../engine/search/SearchUtils.java | 26 +- .../engine/{ => search}/antlr4/QueryLang.g4 | 19 +- .../engine/search/enums/ComparisonType.java | 1 + .../engine/search/MongoDbUtils.java | 18 + .../engine/search/QueryLangTest.java | 693 +++++++++++++++++- .../engine/search/SearchCaseTest.java | 197 +++-- .../engine/search/SearchProcessTest.java | 3 + .../engine/search/SearchTaskTest.java | 411 +++++++++++ .../petriNets/search/search_test.xml | 36 +- .../petriNets/search/search_test2.xml | 18 +- 12 files changed, 1375 insertions(+), 109 deletions(-) rename src/main/java/com/netgrif/application/engine/{ => search}/antlr4/QueryLang.g4 (92%) create mode 100644 src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java create mode 100644 src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 9ee50355a83..9dacaa89d38 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -1,7 +1,7 @@ package com.netgrif.application.engine.search; -import com.netgrif.application.engine.antlr4.QueryLangBaseListener; -import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; import com.netgrif.application.engine.auth.domain.QUser; import com.netgrif.application.engine.petrinet.domain.QPetriNet; import com.netgrif.application.engine.search.enums.ComparisonType; @@ -17,6 +17,8 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; import java.sql.Timestamp; import java.time.LocalDateTime; @@ -97,12 +99,20 @@ private void processAndExpression(List<ParseTree> children, ParseTree current) { setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); } - private void processConditionGroup(ParseTree child, ParseTree current) { + private void processConditionGroup(ParseTree child, ParseTree current, Boolean not) { Predicate predicate = getMongoQuery(child); String elasticQuery = getElasticQuery(child); - setMongoQuery(current, (predicate)); - setElasticQuery(current, elasticQuery != null ? "(" + elasticQuery + ")" : null); + if (predicate != null) { + predicate = not ? (predicate).not() : (predicate); + } + + if (elasticQuery != null) { + elasticQuery = not ? "NOT (" + elasticQuery + ")" : "(" + elasticQuery + ")"; + } + + setMongoQuery(current, predicate); + setElasticQuery(current, elasticQuery); } private void processCondition(ParseTree child, ParseTree current, Boolean not) { @@ -195,7 +205,7 @@ public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext @Override public void exitProcessConditionGroup(QueryLangParser.ProcessConditionGroupContext ctx) { - processConditionGroup(ctx.processCondition() != null ? ctx.processCondition() : ctx.processConditions(), ctx); + processConditionGroup(ctx.processCondition() != null ? ctx.processCondition() : ctx.processConditions(), ctx, ctx.NOT() != null); } @Override @@ -228,7 +238,7 @@ public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) @Override public void exitCaseConditionGroup(QueryLangParser.CaseConditionGroupContext ctx) { - processConditionGroup(ctx.caseCondition() != null ? ctx.caseCondition() : ctx.caseConditions(), ctx); + processConditionGroup(ctx.caseCondition() != null ? ctx.caseCondition() : ctx.caseConditions(), ctx, ctx.NOT() != null); } @Override @@ -261,7 +271,7 @@ public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) @Override public void exitTaskConditionGroup(QueryLangParser.TaskConditionGroupContext ctx) { - processConditionGroup(ctx.taskCondition() != null ? ctx.taskCondition() : ctx.taskConditions(), ctx); + processConditionGroup(ctx.taskCondition() != null ? ctx.taskCondition() : ctx.taskConditions(), ctx, ctx.NOT() != null); } @Override @@ -294,7 +304,7 @@ public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) @Override public void exitUserConditionGroup(QueryLangParser.UserConditionGroupContext ctx) { - processConditionGroup(ctx.userCondition() != null ? ctx.userCondition() : ctx.userConditions(), ctx); + processConditionGroup(ctx.userCondition() != null ? ctx.userCondition() : ctx.userConditions(), ctx, ctx.NOT() != null); } @Override @@ -324,29 +334,30 @@ public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { @Override public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { - StringPath stringPath; - Token op = ctx.stringComparison().op; - String string = getStringValue(ctx.stringComparison().STRING().getText()); + QObjectId qObjectId; + Token op = ctx.objectIdComparison().op; + checkOp(ComparisonType.ID, op); + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); switch (type) { case PROCESS: - stringPath = QPetriNet.petriNet.stringId; + qObjectId = QPetriNet.petriNet.id; break; case CASE: - stringPath = QCase.case$.stringId; - setElasticQuery(ctx, buildElasticQuery("stringId", op, string)); + qObjectId = QCase.case$.id; + setElasticQuery(ctx, buildElasticQuery("stringId", op, objectId.toString())); break; case TASK: - stringPath = QTask.task.stringId; + qObjectId = QTask.task.id; break; case USER: - stringPath = QUser.user.stringId; + qObjectId = QUser.user.id; break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId)); } @Override @@ -487,8 +498,10 @@ public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { switch (ctx.state.getType()) { case QueryLangParser.ENABLED: setMongoQuery(ctx, QTask.task.state.eq(State.ENABLED)); + break; case QueryLangParser.DISABLED: setMongoQuery(ctx, QTask.task.state.eq(State.DISABLED)); + break; } } @@ -632,7 +645,7 @@ public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { String fieldId = ctx.dataOptions().fieldId.getText(); Token op = ctx.stringComparison().op; - checkOp(ComparisonType.BOOLEAN, op); + checkOp(ComparisonType.STRING, op); String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index cb7f9190fd0..e77f520a0a2 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -16,6 +16,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; import java.util.List; @@ -76,13 +77,14 @@ public Object search(String input) { if (evaluator.getMultiple()) { return petriNetRepository.findAll(predicate, new FullPageRequest()).getContent(); } - return petriNetRepository.findOne(predicate); + return petriNetRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); case CASE: if (predicate != null) { if (evaluator.getMultiple()) { return workflowService.searchAll(predicate).getContent(); } - return workflowService.searchOne(predicate); + return workflowService.searchAll(predicate).getContent().stream().findFirst().orElse(null); } List<Case> cases = findCasesElastic(elasticQuery); @@ -91,12 +93,13 @@ public Object search(String input) { if (evaluator.getMultiple()) { return taskService.searchAll(predicate).getContent(); } - return taskService.searchOne(predicate); + return taskService.searchAll(predicate).getContent().stream().findFirst().orElse(null); case USER: if (evaluator.getMultiple()) { return userRepository.findAll(predicate, new FullPageRequest()).getContent(); } - return userRepository.findOne(predicate).orElse(null); + return userRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); } return null; } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java index 36884471d8f..d2983460186 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java @@ -1,7 +1,7 @@ package com.netgrif.application.engine.search; -import com.netgrif.application.engine.antlr4.QueryLangLexer; -import com.netgrif.application.engine.antlr4.QueryLangParser; +import com.netgrif.application.engine.search.antlr4.QueryLangLexer; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; import com.netgrif.application.engine.petrinet.domain.QPetriNet; import com.netgrif.application.engine.petrinet.domain.version.QVersion; import com.netgrif.application.engine.petrinet.domain.version.Version; @@ -13,6 +13,8 @@ import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTreeWalker; +import org.bson.types.ObjectId; +import org.bson.types.QObjectId; import java.time.LocalDate; import java.time.LocalDateTime; @@ -24,12 +26,13 @@ public class SearchUtils { public static final Map<ComparisonType, List<Integer>> comparisonOperators = Map.of( + ComparisonType.ID, List.of(QueryLangParser.EQ), ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS), ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ), - ComparisonType.OPTIONS, List.of(QueryLangParser.CONTAINS) + ComparisonType.OPTIONS, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS) ); public static String toDateString(LocalDate localDate) { @@ -95,12 +98,29 @@ public static String getStringValue(String queryLangString) { return queryLangString.replace("'", ""); } + public static ObjectId getObjectIdValue(String queryLangString) { + String objectId = getStringValue(queryLangString); + if (ObjectId.isValid(objectId)) { + return new ObjectId(objectId); + } + + throw new IllegalArgumentException("Invalid objectId: " + objectId); + } + public static void checkOp(ComparisonType type, Token op) { if (!comparisonOperators.get(type).contains(op.getType())) { throw new IllegalArgumentException("Operator " + op.getText() + " is not applicable for type " + type.toString()); } } + public static Predicate buildObjectIdPredicate(QObjectId qObjectId, Token op, ObjectId objectId) { + if (op.getType() == QueryLangParser.EQ) { + return qObjectId.eq(objectId); + } + + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for id comparison"); + } + public static Predicate buildStringPredicate(StringPath stringPath, Token op, String string) { switch (op.getType()) { case QueryLangParser.EQ: diff --git a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 similarity index 92% rename from src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 rename to src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 797f05934ed..0fc424ea8e5 100644 --- a/src/main/java/com/netgrif/application/engine/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -9,25 +9,25 @@ query: resource=(PROCESS | PROCESSES) delimeter processConditions EOF # processQ processConditions: processOrExpression ; processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; -processConditionGroup: processCondition | '(' SPACE? processConditions SPACE? ')' SPACE? ; +processConditionGroup: processCondition | (NOT SPACE)? '(' SPACE? processConditions SPACE? ')' SPACE? ; processCondition: (NOT SPACE)? processComparisons SPACE? ; caseConditions: caseOrExpression ; caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; -caseConditionGroup: caseCondition | '(' SPACE? caseConditions SPACE? ')' SPACE? ; +caseConditionGroup: caseCondition | (NOT SPACE)? '(' SPACE? caseConditions SPACE? ')' SPACE? ; caseCondition: (NOT SPACE)? caseComparisons SPACE? ; taskConditions: taskOrExpression ; taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; -taskConditionGroup: taskCondition | '(' SPACE? taskConditions SPACE? ')' SPACE? ; +taskConditionGroup: taskCondition | (NOT SPACE)? '(' SPACE? taskConditions SPACE? ')' SPACE? ; taskCondition: (NOT SPACE)? taskComparisons SPACE? ; userConditions: userOrExpression ; userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; -userConditionGroup: userCondition | '(' SPACE? userConditions SPACE? ')' SPACE? ; +userConditionGroup: userCondition | (NOT SPACE)? '(' SPACE? userConditions SPACE? ')' SPACE? ; userCondition: (NOT SPACE)? userComparisons SPACE? ; // delimeter @@ -74,13 +74,13 @@ userComparisons: idComparison ; // attribute comparisons -idComparison: ID SPACE stringComparison ; +idComparison: ID SPACE objectIdComparison ; titleComparison: TITLE SPACE stringComparison ; identifierComparison: IDENTIFIER SPACE stringComparison ; versionComparison: VERSION SPACE op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; creationDateComparison: CREATION_DATE SPACE dateComparison # cdDate | CREATION_DATE SPACE dateTimeComparison # cdDateTime - ; // todo NAE-1997: date/datetime? + ; processIdComparison: PROCESS_ID SPACE stringComparison ; processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison ; authorComparison: AUTHOR SPACE stringComparison ; @@ -90,10 +90,10 @@ userIdComparison: USER_ID SPACE stringComparison ; caseIdComparison: CASE_ID SPACE stringComparison ; lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDate | LAST_ASSIGN SPACE dateTimeComparison # laDateTime - ; // todo NAE-1997: date/datetime? + ; lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDate | LAST_FINISH SPACE dateTimeComparison # lfDateTime - ; // todo NAE-1997: date/datetime? + ; nameComparison: NAME SPACE stringComparison ; surnameComparison: SURNAME SPACE stringComparison ; emailComparison: EMAIL SPACE stringComparison ; @@ -103,12 +103,13 @@ dataValueComparison: dataValue SPACE stringComparison # dataString | dataValue SPACE dateTimeComparison # dataDatetime | dataValue SPACE booleanComparison # dataBoolean ; -dataOptionsComparison: dataOptions stringComparison ; +dataOptionsComparison: dataOptions SPACE stringComparison ; placesComparison: places SPACE numberComparison ; tasksStateComparison: tasksState SPACE op=EQ SPACE state=(ENABLED | DISABLED) ; tasksUserIdComparison: tasksUserId SPACE stringComparison ; // basic comparisons +objectIdComparison: op=EQ SPACE STRING ; stringComparison: op=(EQ | CONTAINS) SPACE STRING ; numberComparison: op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; dateComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; diff --git a/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java index 27494d99a35..a013863f7e5 100644 --- a/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java +++ b/src/main/java/com/netgrif/application/engine/search/enums/ComparisonType.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.search.enums; public enum ComparisonType { + ID, STRING, NUMBER, DATE, diff --git a/src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java new file mode 100644 index 00000000000..a96b084bb89 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java @@ -0,0 +1,18 @@ +package com.netgrif.application.engine.search; + +import com.querydsl.core.types.Predicate; + +import org.bson.Document; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery; + +public class MongoDbUtils<T> extends SpringDataMongodbQuery<T> { + + public MongoDbUtils(MongoOperations operations, Class<? extends T> type) { + super(operations, type, operations.getCollectionName(type)); + } + + public Document convertPredicateToDocument(Predicate predicate) { + return this.createQuery(predicate); + } +} diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index dba0a547a05..90bf1a33570 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -1,13 +1,26 @@ package com.netgrif.application.engine.search; +import com.netgrif.application.engine.auth.domain.QUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.QPetriNet; +import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.workflow.domain.*; +import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; +import org.bson.Document; +import org.bson.types.ObjectId; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.time.LocalDateTime; + import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; @Slf4j @@ -15,12 +28,642 @@ @ActiveProfiles({"test"}) @ExtendWith(SpringExtension.class) public class QueryLangTest { + public static final ObjectId GENERIC_OBJECT_ID = ObjectId.get(); + + @Autowired + MongoOperations mongoOperations; + + // todo NAE-1997:: simple queries logical elastic string query comparison + // todo NAE-1997:: complex queries logical elastic string query comparison + + @Test + public void testSimpleMongodbProcessQuery() { + MongoDbUtils<PetriNet> mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // identifier comparison + actual = evaluateQuery("process: identifier eq 'test'").getFullMongoQuery(); + expected = QPetriNet.petriNet.identifier.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: identifier contains 'test'").getFullMongoQuery(); + expected = QPetriNet.petriNet.identifier.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.eq(new Version(1, 1, 1)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.lt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.lt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.lt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version lte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.lt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.lt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.lt(1)))) + .or(QPetriNet.petriNet.version.eq(new Version(1, 1, 1))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gt 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1)))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: version gte 1.1.1").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1)))) + .or(QPetriNet.petriNet.version.eq(new Version(1, 1, 1))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // title comparison + actual = evaluateQuery("process: title eq 'test'").getFullMongoQuery(); + expected = QPetriNet.petriNet.title.defaultValue.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: title contains 'test'").getFullMongoQuery(); + expected = QPetriNet.petriNet.title.defaultValue.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // creationDate comparison + actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QPetriNet.petriNet.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QPetriNet.petriNet.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QPetriNet.petriNet.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QPetriNet.petriNet.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testComplexMongodbProcessQuery() { + MongoDbUtils<PetriNet> mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("process: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test")); + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").or(QPetriNet.petriNet.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID) + .and(QPetriNet.petriNet.title.defaultValue.eq("test") + .or(QPetriNet.petriNet.title.defaultValue.eq("test1").and(QPetriNet.petriNet.identifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbCaseQuery() { + MongoDbUtils<Case> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QCase.case$.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery("case: processId eq 'test'").getFullMongoQuery(); + expected = QCase.case$.petriNetId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: processId contains 'test'").getFullMongoQuery(); + expected = QCase.case$.petriNetId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processIdentifier comparison + actual = evaluateQuery("case: processIdentifier eq 'test'").getFullMongoQuery(); + expected = QCase.case$.processIdentifier.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: processIdentifier contains 'test'").getFullMongoQuery(); + expected = QCase.case$.processIdentifier.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // title comparison + actual = evaluateQuery("case: title eq 'test'").getFullMongoQuery(); + expected = QCase.case$.title.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: title contains 'test'").getFullMongoQuery(); + expected = QCase.case$.title.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // creationDate comparison + actual = evaluateQuery("case: creationDate eq 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: creationDate lt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QCase.case$.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: creationDate lte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QCase.case$.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: creationDate gt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QCase.case$.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: creationDate gte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QCase.case$.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("case: author contains 'test'").getFullMongoQuery(); + expected = QCase.case$.author.id.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // only available for elastic query + // places comparison + actual = evaluateQuery("case: places.p1.marking eq 1").getFullMongoQuery(); + assert actual == null; + + // task state comparison + actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullMongoQuery(); + assert actual == null; + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullMongoQuery(); + assert actual == null; + + // data value comparison + actual = evaluateQuery("case: data.field1.value eq 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value contains 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lt 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lte 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gt 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gte 1").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.value eq true").getFullMongoQuery(); + assert actual == null; + + // data options comparison + actual = evaluateQuery("case: data.field1.options eq 'test'").getFullMongoQuery(); + assert actual == null; + + actual = evaluateQuery("case: data.field1.options contains 'test'").getFullMongoQuery(); + assert actual == null; + } + + @Test + public void testComplexMongodbCaseQuery() { + MongoDbUtils<Case> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("case: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").or(QCase.case$.title.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.eq(GENERIC_OBJECT_ID) + .and(QCase.case$.title.eq("test") + .or(QCase.case$.title.eq("test1").and(QCase.case$.processIdentifier.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbTaskQuery() { + MongoDbUtils<Task> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QTask.task.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // transitionId comparison + actual = evaluateQuery("task: transitionId eq 'test'").getFullMongoQuery(); + expected = QTask.task.transitionId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: transitionId contains 'test'").getFullMongoQuery(); + expected = QTask.task.transitionId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // title comparison + actual = evaluateQuery("task: title eq 'test'").getFullMongoQuery(); + expected = QTask.task.title.defaultValue.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: title contains 'test'").getFullMongoQuery(); + expected = QTask.task.title.defaultValue.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // state comparison + actual = evaluateQuery("task: state eq enabled").getFullMongoQuery(); + expected = QTask.task.state.eq(State.ENABLED); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: state eq disabled").getFullMongoQuery(); + expected = QTask.task.state.eq(State.DISABLED); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullMongoQuery(); + expected = QTask.task.userId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: userId contains 'test'").getFullMongoQuery(); + expected = QTask.task.userId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: caseId contains 'test'").getFullMongoQuery(); + expected = QTask.task.caseId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullMongoQuery(); + expected = QTask.task.processId.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: processId contains 'test'").getFullMongoQuery(); + expected = QTask.task.processId.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // lastAssign comparison + actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); - // todo NAE-1997:: simple queries logical predicate comparison - // todo NAE-1997:: complex queries logical predicate comparison + actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastAssigned.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - // todo NAE-1997:: all attributes success - // todo NAE-1997:: all comparison type fail + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastAssigned.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastAssigned.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastAssigned.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // lastFinish comparison + actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastFinished.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastFinished.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastFinished.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullMongoQuery(); + expected = QTask.task.lastFinished.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) + .or(QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testComplexMongodbTaskQuery() { + MongoDbUtils<Task> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("task: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QTask.task.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").or(QTask.task.title.defaultValue.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.eq(GENERIC_OBJECT_ID) + .and(QTask.task.title.defaultValue.eq("test") + .or(QTask.task.title.defaultValue.eq("test1").and(QTask.task.processId.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testSimpleMongodbUserQuery() { + MongoDbUtils<User> mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // id comparison + Predicate actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QUser.user.id.eq(GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // name comparison + actual = evaluateQuery("user: name eq 'test'").getFullMongoQuery(); + expected = QUser.user.name.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("user: name contains 'test'").getFullMongoQuery(); + expected = QUser.user.name.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // surname comparison + actual = evaluateQuery("user: surname eq 'test'").getFullMongoQuery(); + expected = QUser.user.surname.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("user: surname contains 'test'").getFullMongoQuery(); + expected = QUser.user.surname.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // email comparison + actual = evaluateQuery("user: email eq 'test'").getFullMongoQuery(); + expected = QUser.user.email.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery("user: email contains 'test'").getFullMongoQuery(); + expected = QUser.user.email.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + @Test + public void testComplexMongodbUserQuery() { + MongoDbUtils<User> mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); + + // not comparison + Predicate actual = evaluateQuery(String.format("user: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate expected = QUser.user.id.eq(GENERIC_OBJECT_ID).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or not email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test").not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1"))); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").or(QUser.user.email.eq("test1")).not()); + + compareMongoQueries(mongoDbUtils, actual, expected); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.eq(GENERIC_OBJECT_ID) + .and(QUser.user.email.eq("test") + .or(QUser.user.email.eq("test1").and(QUser.user.name.eq("test")))); + + compareMongoQueries(mongoDbUtils, actual, expected); + } @Test public void testProcessQueriesFail() { @@ -65,6 +708,7 @@ public void testTaskQueriesFail() { // using process, case, user attributes Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: identifier eq 'test'")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: creationDate eq 2020-03-03")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: processIdentifier eq 'test'")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: places.p1.marking eq 1")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("task: tasks.t1.state eq enabled")); @@ -79,8 +723,9 @@ public void testTaskQueriesFail() { @Test public void testUserQueriesFail() { // using process, case, task attributes - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier eq 'test'")); - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: identifier eq 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: version eq 1.1.1")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: creationDate eq 2020-03-03")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processId eq 'test'")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: processIdentifier eq 'test'")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: author eq 'test'")); @@ -96,4 +741,40 @@ public void testUserQueriesFail() { Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastAssign eq 2020-03-03")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("user: lastFinish eq 2020-03-03")); } + + @Test + public void testComparisonTypeFail() { + // id comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id contains 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id lte 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gte 'test'")); + + // string comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier lt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier lte 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier gt 'test'")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier gte 'test'")); + + // number comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); + + // date/datetime comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: creationDate contains 2020-03-03")); + + // boolean comparison + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value contains true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lt true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value lte true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gt true")); + Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gte true")); + } + + private void compareMongoQueries(MongoDbUtils<?> mongoDbUtils, Predicate actual, Predicate expected) { + Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); + Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); + + assert actualDocument.equals(expectedDocument); + } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index ce725089b64..48de63a153c 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -36,6 +36,8 @@ @ExtendWith(SpringExtension.class) public class SearchCaseTest { + public static final String TEST_TRANSITION_ID = "search_test_t1"; + @Autowired private ImportHelper importHelper; @@ -314,9 +316,9 @@ public void testSearchByTaskState() { importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - String query = String.format("case: tasks.t1.state eq %s", "disabled"); - String queryOther = String.format("case: tasks.t1.state eq %s", "enabled"); - String queryMore = String.format("cases: tasks.t1.state eq %s", "disabled"); + String query = String.format("case: tasks.%s.state eq %s", TEST_TRANSITION_ID, "disabled"); + String queryOther = String.format("case: tasks.%s.state eq %s", TEST_TRANSITION_ID, "enabled"); + String queryMore = String.format("cases: tasks.%s.state eq %s", TEST_TRANSITION_ID, "disabled"); long count = searchService.count(query); assert count == 2; @@ -352,9 +354,9 @@ public void testSearchByTaskUserId() { importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); - String query = String.format("case: tasks.t1.userId eq '%s'", user1.getStringId()); - String queryOther = String.format("case: tasks.t1.userId eq '%s'", user2.getStringId()); - String queryMore = String.format("cases: tasks.t1.userId eq '%s'", user1.getStringId()); + String query = String.format("case: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user1.getStringId()); + String queryOther = String.format("case: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user2.getStringId()); + String queryMore = String.format("cases: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user1.getStringId()); long count = searchService.count(query); assert count == 2; @@ -375,42 +377,6 @@ public void testSearchByTaskUserId() { assert ((List<Case>) cases).containsAll(List.of(case1, case2)); } - // todo: change values - -// BooleanField booleanTrue = new BooleanField(); -// booleanTrue.setRawValue(true); -// BooleanField booleanFalse = new BooleanField(); -// booleanFalse.setRawValue(false); -// TextField textField = new TextField(); -// textField.setRawValue("test"); -// Map<String, I18nString> options = Map.of("test1", new I18nString("Test1"), "test2", new I18nString("Test2"), "test3", new I18nString("Test3")); -// EnumerationMapField enumerationMapField = new EnumerationMapField(); -// enumerationMapField.setOptions(options); -// enumerationMapField.setRawValue("test1"); -// MultichoiceMapField multichoiceMapField = new MultichoiceMapField(); -// multichoiceMapField.setOptions(options); -// multichoiceMapField.setRawValue(Set.of("test1", "test2")); -// NumberField numberField = new NumberField(); -// numberField.setRawValue(2.0); -// DateField dateField = new DateField(); -// dateField.setRawValue(LocalDate.now()); -// DateTimeField dateTimeField = new DateTimeField(); -// dateTimeField.setRawValue(LocalDateTime.now()); - -// importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); -// importHelper.setTaskData("Test", case1.getStringId(), new DataSet(Map.of( -// "boolean_immediate", booleanTrue, -// "text_immediate", textField, -// "number_immediate", numberField, -// "multichoice_immediate", multichoiceMapField, -// "enumeration_immediate", enumerationMapField, -// "date_immediate", dateField, -// "date_time_immediate", dateTimeField -// ))); -// importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); -// importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); -// importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - @Test public void testSearchByDataValue() { PetriNet net = importPetriNet("search/search_test.xml"); @@ -418,8 +384,37 @@ public void testSearchByDataValue() { IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net); + + BooleanField booleanFalse = new BooleanField(); + booleanFalse.setRawValue(false); + TextField textField = new TextField(); + textField.setRawValue("other"); + Map<String, I18nString> options = Map.of("test1", new I18nString("Test1"), "test2", new I18nString("Test2"), "test3", new I18nString("Test3")); + EnumerationMapField enumerationMapField = new EnumerationMapField(); + enumerationMapField.setOptions(options); + enumerationMapField.setRawValue("test1"); + MultichoiceMapField multichoiceMapField = new MultichoiceMapField(); + multichoiceMapField.setOptions(options); + multichoiceMapField.setRawValue(Set.of("test1", "test2")); + NumberField numberField = new NumberField(); + numberField.setRawValue(2.0); + DateField dateField = new DateField(); + dateField.setRawValue(LocalDate.now().minusDays(5)); + DateTimeField dateTimeField = new DateTimeField(); + dateTimeField.setRawValue(LocalDateTime.now().minusDays(5)); - + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.setTaskData("Test", case2.getStringId(), new DataSet(Map.of( + "boolean_immediate", booleanFalse, + "text_immediate", textField, + "number_immediate", numberField, + "multichoice_immediate", multichoiceMapField, + "enumeration_immediate", enumerationMapField, + "date_immediate", dateField, + "date_time_immediate", dateTimeField + ))); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); String queryTextEq = String.format("case: data.text_immediate.value eq %s", "'test'"); String queryTextContains = String.format("case: data.text_immediate.value contains %s", "'es'"); @@ -444,7 +439,6 @@ public void testSearchByDataValue() { String queryDateTimeGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); String queryDateTimeGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); - long count = searchService.count(queryTextEq); assert count == 1; @@ -470,10 +464,10 @@ public void testSearchByDataValue() { assert count == 1; count = searchService.count(queryNumberLt); - assert count == 1; + assert count == 2; count = searchService.count(queryNumberLte); - assert count == 1; + assert count == 2; count = searchService.count(queryNumberGt); assert count == 1; @@ -485,10 +479,10 @@ public void testSearchByDataValue() { assert count == 1; count = searchService.count(queryDateLt); - assert count == 1; + assert count == 2; count = searchService.count(queryDateLte); - assert count == 1; + assert count == 2; count = searchService.count(queryDateGt); assert count == 1; @@ -500,10 +494,10 @@ public void testSearchByDataValue() { assert count == 1; count = searchService.count(queryDateTimeLt); - assert count == 1; + assert count == 2; count = searchService.count(queryDateTimeLte); - assert count == 1; + assert count == 2; count = searchService.count(queryDateTimeGt); assert count == 1; @@ -519,7 +513,108 @@ public void testSearchByDataValue() { assert foundCase instanceof Case; assert foundCase.equals(case1); - // todo: other assertions + foundCase = searchService.search(queryBoolean); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryEnumerationEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryEnumerationContains); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryMultichoiceEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryMultichoiceContains); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryNumberEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryNumberLt); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryNumberLte); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryNumberGt); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryNumberGte); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateLt); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryDateLte); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryDateGt); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateGte); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateTimeEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateTimeLt); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryDateTimeLte); + assert foundCase instanceof Case; + assert foundCase.equals(case1) || foundCase.equals(case2); + + foundCase = searchService.search(queryDateTimeGt); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryDateTimeGte); + assert foundCase instanceof Case; + assert foundCase.equals(case1); } + @Test + public void testSearchByDataOptions() { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + + String queryEq = String.format("case: data.enumeration_map_immediate.options eq '%s'", "key1"); + String queryContains = String.format("case: data.enumeration_map_immediate.options contains '%s'", "key1"); + + long count = searchService.count(queryEq); + assert count == 1; + + count = searchService.count(queryContains); + assert count == 1; + + Object foundCase = searchService.search(queryEq); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + + foundCase = searchService.search(queryContains); + assert foundCase instanceof Case; + assert foundCase.equals(case1); + } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java index 71ed999349b..86e5e7fdbd4 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -6,11 +6,14 @@ import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.startup.ImportHelper; import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.formula.functions.T; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor; +import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java new file mode 100644 index 00000000000..0e90b5301b8 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -0,0 +1,411 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.TestHelper; +import com.netgrif.application.engine.auth.domain.Authority; +import com.netgrif.application.engine.auth.domain.IUser; +import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.I18nString; +import com.netgrif.application.engine.petrinet.domain.PetriNet; +import com.netgrif.application.engine.petrinet.domain.dataset.*; +import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; +import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.startup.ImportHelper; +import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.Task; +import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; +import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; + +@Slf4j +@SpringBootTest +@ActiveProfiles({"test"}) +@ExtendWith(SpringExtension.class) +public class SearchTaskTest { + public static final String TEST_TRANSITION_ID = "search_test_t1"; + public static final String TEST_TRANSITION2_ID = "search_test_t2"; + + @Autowired + private ImportHelper importHelper; + + @Autowired + private TestHelper testHelper; + + @Autowired + private ISearchService searchService; + + @Autowired + private ITaskService taskService; + + private Map<String, Authority> auths; + + @BeforeEach + void setup() { + testHelper.truncateDbs(); + auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); + } + + private PetriNet importPetriNet(String fileName) { + PetriNet testNet = importHelper.createNet(fileName).orElse(null); + assert testNet != null; + return testNet; + } + + private IUser createUser(String name, String surname, String email, String authority) { + User user = new User(email, "password", name, surname); + Authority[] authorities = new Authority[]{auths.get(authority)}; + ProcessRole[] processRoles = new ProcessRole[]{}; + return importHelper.createUser(user, authorities, processRoles); + } + + @Test + public void testSearchById() { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case caze = importHelper.createCase("Search Test", net); + + String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + + String query = String.format("task: id eq '%s'", taskId); + + long count = searchService.count(query); + assert count == 1; + + Object foundTask = searchService.search(query); + + assert foundTask instanceof Task; + assert foundTask.equals(task); + } + + @Test + public void testSearchByTransitionId() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + + Case caze = importHelper.createCase("Search Test", net); + Case caze2 = importHelper.createCase("Search Test2", net2); + + String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + + String task2Id = caze2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: transitionId eq '%s'", TEST_TRANSITION_ID); + String queryMore = String.format("tasks: transitionId eq '%s'", TEST_TRANSITION_ID); + + long count = searchService.count(query); + assert count == 2; + + Object foundTask = searchService.search(query); + + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByTitle() { + PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + + String query = String.format("task: title eq '%s'", task.getTitle()); + String queryOther = String.format("task: title eq '%s'", task3.getTitle()); + String queryMore = String.format("tasks: title eq '%s'", task.getTitle()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + foundTask = searchService.search(queryOther); + assert foundTask instanceof Task; + assert foundTask.equals(task3); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByState() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String query = String.format("task: state eq %s", "disabled"); + String queryOther = String.format("task: state eq %s", "enabled"); + String queryMore = String.format("tasks: state eq %s", "disabled"); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + foundTask = searchService.search(queryOther); + assert foundTask instanceof Task; + assert foundTask.equals(task3); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByUserId() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); + + String query = String.format("case: tasks.t1.userId eq '%s'", user1.getStringId()); + String queryOther = String.format("case: tasks.t1.userId eq '%s'", user2.getStringId()); + String queryMore = String.format("cases: tasks.t1.userId eq '%s'", user1.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + foundTask = searchService.search(queryOther); + assert foundTask instanceof Task; + assert foundTask.equals(task3); + + Object cases = searchService.search(queryMore); + assert cases instanceof List; + assert ((List<Task>) cases).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByCaseId() { + PetriNet net = importPetriNet("search/search_test.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + + String query = String.format("task: caseId eq '%s'", case1.getStringId()); + String queryOther = String.format("task: caseId eq '%s'", case2.getStringId()); + String queryMore = String.format("tasks: caseId eq '%s'", case1.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + foundTask = searchService.search(queryOther); + assert foundTask instanceof Task; + assert foundTask.equals(task3); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByProcessId() { + PetriNet net = importPetriNet("search/search_test.xml"); + PetriNet net2 = importPetriNet("search/search_test2.xml"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + Case case3 = importHelper.createCase("Search Test2", net2); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task3 = taskService.findOne(task3Id); + + String query = String.format("task: processId eq '%s'", net.getStringId()); + String queryOther = String.format("task: processId eq '%s'", net2.getStringId()); + String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); + + long count = searchService.count(query); + assert count == 2; + + count = searchService.count(queryOther); + assert count == 1; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + foundTask = searchService.search(queryOther); + assert foundTask instanceof Task; + assert foundTask.equals(task3); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByLastAssign() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + + LocalDateTime before = LocalDateTime.now(); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: lastAssign eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); + String queryBefore = String.format("task: lastAssign > %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastAssign > %s", SearchUtils.toDateTimeString(before)); + + long count = searchService.count(query); + assert count == 1; + + count = searchService.count(queryBefore); + assert count == 2; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task); + + foundTask = searchService.search(queryBefore); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } + + @Test + public void testSearchByLastFinish() { + PetriNet net = importPetriNet("search/search_test.xml"); + + IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test", net); + + LocalDateTime before = LocalDateTime.now(); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task = taskService.findOne(taskId); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); + + String query = String.format("task: lastFinish eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); + String queryBefore = String.format("task: lastFinish > %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastFinish > %s", SearchUtils.toDateTimeString(before)); + + long count = searchService.count(query); + assert count == 1; + + count = searchService.count(queryBefore); + assert count == 2; + + Object foundTask = searchService.search(query); + assert foundTask instanceof Task; + assert foundTask.equals(task); + + foundTask = searchService.search(queryBefore); + assert foundTask instanceof Task; + assert foundTask.equals(task) || foundTask.equals(task2); + + Object foundTasks = searchService.search(queryMore); + assert foundTasks instanceof List; + assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + } +} diff --git a/src/test/resources/petriNets/search/search_test.xml b/src/test/resources/petriNets/search/search_test.xml index c4da5e43798..c9184d0b1cc 100644 --- a/src/test/resources/petriNets/search/search_test.xml +++ b/src/test/resources/petriNets/search/search_test.xml @@ -56,12 +56,12 @@ <init dynamic="true">java.time.LocalDateTime.now()</init> </data> <transition> - <id>t1</id> + <id>search_test_t1</id> <x>260</x> <y>100</y> <label>Test</label> <dataGroup> - <id>t1_0</id> + <id>search_test_t1_0</id> <cols>4</cols> <layout>grid</layout> <dataRef> @@ -164,18 +164,24 @@ </dataRef> </dataGroup> <event type="assign"> - <id>t1_assign</id> + <id>search_test_t1_assign</id> </event> <event type="finish"> - <id>t1_finish</id> + <id>search_test_t1_finish</id> </event> <event type="cancel"> - <id>t1_cancel</id> + <id>search_test_t1_cancel</id> </event> <event type="delegate"> - <id>t1_delegate</id> + <id>search_test_t1_delegate</id> </event> </transition> + <transition> + <id>search_test_t2</id> + <x>260</x> + <y>180</y> + <label>Test T2</label> + </transition> <place> <id>p1</id> <x>180</x> @@ -194,14 +200,28 @@ <id>a1</id> <type>regular</type> <sourceId>p1</sourceId> - <destinationId>t1</destinationId> + <destinationId>search_test_t1</destinationId> <multiplicity>1</multiplicity> </arc> <arc> <id>a2</id> <type>regular</type> - <sourceId>t1</sourceId> + <sourceId>search_test_t1</sourceId> <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a3</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a4</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t2</destinationId> <multiplicity>1</multiplicity> </arc> </document> \ No newline at end of file diff --git a/src/test/resources/petriNets/search/search_test2.xml b/src/test/resources/petriNets/search/search_test2.xml index f6fe37525c5..a2d9ed2def1 100644 --- a/src/test/resources/petriNets/search/search_test2.xml +++ b/src/test/resources/petriNets/search/search_test2.xml @@ -56,12 +56,12 @@ <init dynamic="true">java.time.LocalDateTime.now()</init> </data> <transition> - <id>t1</id> + <id>search_test_t1</id> <x>260</x> <y>100</y> - <label>Test</label> + <label>Test2</label> <dataGroup> - <id>t1_0</id> + <id>search_test_t1_0</id> <cols>4</cols> <layout>grid</layout> <dataRef> @@ -164,16 +164,16 @@ </dataRef> </dataGroup> <event type="assign"> - <id>t1_assign</id> + <id>search_test_t1_asign</id> </event> <event type="finish"> - <id>t1_finish</id> + <id>search_test_t1_fiish</id> </event> <event type="cancel"> - <id>t1_cancel</id> + <id>search_test_t1_cacel</id> </event> <event type="delegate"> - <id>t1_delegate</id> + <id>search_test_t1_deleate</id> </event> </transition> <place> @@ -194,13 +194,13 @@ <id>a1</id> <type>regular</type> <sourceId>p1</sourceId> - <destinationId>t1</destinationId> + <destinationId>search_test_t1</destinationId> <multiplicity>1</multiplicity> </arc> <arc> <id>a2</id> <type>regular</type> - <sourceId>t1</sourceId> + <sourceId>search_test_t1</sourceId> <destinationId>p2</destinationId> <multiplicity>1</multiplicity> </arc> From 1ab46408a2b613cac72ff64eec09e56f6cce763a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Thu, 30 Jan 2025 21:11:21 +0100 Subject: [PATCH 13/27] [NAE-1997] Query language - add search by id (ObjectId) - fix tests for process, task, test --- .../engine/search/QueryLangEvaluator.java | 4 +- .../engine/search/QueryLangTest.java | 1 + .../engine/search/SearchProcessTest.java | 131 +++++++++----- .../engine/search/SearchTaskTest.java | 171 +++++++++--------- .../engine/search/SearchUserTest.java | 50 +++-- .../search/{ => utils}/MongoDbUtils.java | 2 +- .../petriNets/search/search_test.xml | 7 - 7 files changed, 208 insertions(+), 158 deletions(-) rename src/test/java/com/netgrif/application/engine/search/{ => utils}/MongoDbUtils.java (91%) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 9dacaa89d38..6aeb7ed415e 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -30,8 +30,8 @@ public class QueryLangEvaluator extends QueryLangBaseListener { - ParseTreeProperty<String> elasticQuery = new ParseTreeProperty<>(); - ParseTreeProperty<Predicate> mongoQuery = new ParseTreeProperty<>(); + private final ParseTreeProperty<String> elasticQuery = new ParseTreeProperty<>(); + private final ParseTreeProperty<Predicate> mongoQuery = new ParseTreeProperty<>(); @Getter private QueryType type; diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 90bf1a33570..d0c792f5736 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -5,6 +5,7 @@ import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.QPetriNet; import com.netgrif.application.engine.petrinet.domain.version.Version; +import com.netgrif.application.engine.search.utils.MongoDbUtils; import com.netgrif.application.engine.workflow.domain.*; import com.querydsl.core.types.Predicate; import lombok.extern.slf4j.Slf4j; diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java index 86e5e7fdbd4..876f486f36b 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -3,21 +3,21 @@ import com.netgrif.application.engine.TestHelper; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.VersionType; +import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.startup.ImportHelper; import lombok.extern.slf4j.Slf4j; -import org.apache.poi.ss.formula.functions.T; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.mongodb.repository.support.QuerydslMongoPredicateExecutor; -import org.springframework.data.mongodb.repository.support.SpringDataMongodbQuery; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; @@ -26,6 +26,8 @@ @ActiveProfiles({"test"}) @ExtendWith(SpringExtension.class) public class SearchProcessTest { + @Autowired + private PetriNetRepository petriNetRepository; @Autowired private ImportHelper importHelper; @@ -39,6 +41,12 @@ public class SearchProcessTest { @BeforeEach void setup() { testHelper.truncateDbs(); + List<String> idsToDelete = new ArrayList<>(); + idsToDelete.addAll(petriNetRepository.findAllByIdentifier("search_test").stream().map(PetriNet::getStringId).collect(Collectors.toList())); + idsToDelete.addAll(petriNetRepository.findAllByIdentifier("search_test2").stream().map(PetriNet::getStringId).collect(Collectors.toList())); + if (!idsToDelete.isEmpty()) { + petriNetRepository.deleteAllById(idsToDelete); + } } private PetriNet importPetriNet(String fileName, VersionType versionType) { @@ -47,10 +55,40 @@ private PetriNet importPetriNet(String fileName, VersionType versionType) { return testNet; } + private static PetriNet convertToPetriNet(Object petriNetObject) { + assert petriNetObject instanceof PetriNet; + return (PetriNet) petriNetObject; + } + + private static List<PetriNet> convertToPetriNetList(Object petriNetListObject) { + assert petriNetListObject instanceof List<?>; + for (Object petriNetObject : (List<?>) petriNetListObject) { + assert petriNetObject instanceof PetriNet; + } + + return (List<PetriNet>) petriNetListObject; + } + + private void comparePetriNets(PetriNet actual, PetriNet expected) { + assert actual.getStringId().equals(expected.getStringId()); + } + + private void comparePetriNets(PetriNet actual, List<PetriNet> expected) { + List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + + assert expectedStringIds.contains(actual.getStringId()); + } + + private void comparePetriNets(List<PetriNet> actual, List<PetriNet> expected) { + List<String> actualStringIds = actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + + assert actualStringIds.containsAll(expectedStringIds); + } + @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); - PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); String query = String.format("process: id eq '%s'", net.getStringId()); @@ -59,8 +97,7 @@ public void testSearchById() { Object process = searchService.search(query); - assert process instanceof PetriNet; - assert process.equals(net); + comparePetriNets(convertToPetriNet(process), net); } @Test @@ -76,12 +113,10 @@ public void testSearchByIdentifier() { assert count == 2; Object process = searchService.search(query); - assert process instanceof PetriNet; - assert process.equals(net) || process.equals(netNewer); + comparePetriNets(convertToPetriNet(process), List.of(net, netNewer)); Object processes = searchService.search(queryMore); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewer)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); } @Test @@ -90,50 +125,46 @@ public void testSearchByVersion() { PetriNet netNewerPatch = importPetriNet("search/search_test.xml", VersionType.PATCH); PetriNet netNewerMinor = importPetriNet("search/search_test.xml", VersionType.MINOR); PetriNet netNewerMajor = importPetriNet("search/search_test.xml", VersionType.MAJOR); - PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); - String queryEq = String.format("process: version eq '%s'", "1.0.0"); - String queryLt = String.format("processes: version lt '%s'", "2.0.0"); - String queryLte = String.format("processes: version lte '%s'", "2.0.0"); - String queryGt = String.format("processes: version gt '%s'", "1.0.0"); - String queryGte = String.format("processes: version gt '%s'", "1.0.0"); + String queryEq = String.format("process: identifier eq '%s' and version eq %s", net.getIdentifier(), "1.0.0"); + String queryLt = String.format("processes: identifier eq '%s' and version lt %s", net.getIdentifier(), "2.0.0"); + String queryLte = String.format("processes: identifier eq '%s' and version lte %s", net.getIdentifier(), "2.0.0"); + String queryGt = String.format("processes: identifier eq '%s' and version gt %s", net.getIdentifier(), "1.0.0"); + String queryGte = String.format("processes: identifier eq '%s' and version gte %s", net.getIdentifier(), "1.0.0"); long count = searchService.count(queryEq); assert count == 1; Object process = searchService.search(queryEq); - assert process instanceof PetriNet; - assert process.equals(net); + comparePetriNets(convertToPetriNet(process), List.of(net)); count = searchService.count(queryLt); assert count == 3; Object processes = searchService.search(queryLt); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor)); - assert !((List<PetriNet>) processes).contains(netNewerMajor); + List<PetriNet> actual = convertToPetriNetList(processes); + comparePetriNets(actual, List.of(net, netNewerPatch, netNewerMinor)); + assert !actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()).contains(netNewerMajor.getStringId()); count = searchService.count(queryLte); assert count == 4; processes = searchService.search(queryLte); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); count = searchService.count(queryGt); assert count == 3; processes = searchService.search(queryGt); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(netNewerPatch, netNewerMinor, netNewerMajor)); - assert !((List<PetriNet>) processes).contains(net); + actual = convertToPetriNetList(processes); + comparePetriNets(actual, List.of(netNewerPatch, netNewerMinor, netNewerMajor)); + assert !actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()).contains(net.getStringId()); count = searchService.count(queryGte); assert count == 4; processes = searchService.search(queryGte); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); } @Test @@ -143,66 +174,66 @@ public void testSearchByTitle() { PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); String query = String.format("process: title eq '%s'", net.getTitle().toString()); + String queryOther = String.format("process: title eq '%s'", net2.getTitle().toString()); String queryMore = String.format("processes: title eq '%s'", net.getTitle().toString()); long count = searchService.count(query); assert count == 2; Object process = searchService.search(query); - assert process instanceof PetriNet; - assert process.equals(net) || process.equals(netNewer); + comparePetriNets(convertToPetriNet(process), List.of(net, netNewer)); + + count = searchService.count(queryOther); + assert count == 1; + + process = searchService.search(queryOther); + comparePetriNets(convertToPetriNet(process), net2); Object processes = searchService.search(queryMore); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewer)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); } @Test public void testSearchByCreationDate() { PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); - PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + PetriNet netNewest = importPetriNet("search/search_test.xml", VersionType.MAJOR); - String queryEq = String.format("process: creationDate eq '%s'", toDateTimeString(net.getCreationDate())); - String queryLt = String.format("processes: creationDate lt '%s'", toDateTimeString(net2.getCreationDate())); - String queryLte = String.format("processes: creationDate lte '%s'", toDateTimeString(net2.getCreationDate())); - String queryGt = String.format("processes: creationDate gt '%s'", toDateTimeString(net.getCreationDate())); - String queryGte = String.format("processes: creationDate gte '%s'", toDateTimeString(net.getCreationDate())); + String queryEq = String.format("process: identifier eq '%s' and creationDate eq %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); + String queryLt = String.format("processes: identifier eq '%s' and creationDate lt %s", net.getIdentifier(), toDateTimeString(netNewest.getCreationDate())); + String queryLte = String.format("processes: identifier eq '%s' and creationDate lte %s", net.getIdentifier(), toDateTimeString(netNewest.getCreationDate())); + String queryGt = String.format("processes: identifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); + String queryGte = String.format("processes: identifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); long count = searchService.count(queryEq); assert count == 1; Object process = searchService.search(queryEq); - assert process instanceof PetriNet; - assert process.equals(net); + comparePetriNets(convertToPetriNet(process), net); count = searchService.count(queryLt); assert count == 2; Object processes = searchService.search(queryLt); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewer)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); count = searchService.count(queryLte); - assert count == 2; + assert count == 3; processes = searchService.search(queryLte); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewer, net2)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); count = searchService.count(queryGt); assert count == 2; processes = searchService.search(queryGt); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(netNewer, net2)); + comparePetriNets(convertToPetriNetList(processes), List.of(netNewer, netNewest)); count = searchService.count(queryGte); - assert count == 2; + assert count == 3; processes = searchService.search(queryGte); - assert processes instanceof List; - assert ((List<PetriNet>) processes).containsAll(List.of(net, netNewer, net2)); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index 0e90b5301b8..2300a9a70b5 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -4,16 +4,13 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.User; -import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.dataset.*; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.Task; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; -import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -23,13 +20,10 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; -import java.util.Set; - -import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; +import java.util.stream.Collectors; @Slf4j @SpringBootTest @@ -72,6 +66,38 @@ private IUser createUser(String name, String surname, String email, String autho return importHelper.createUser(user, authorities, processRoles); } + + private static Task convertToTask(Object taskObject) { + assert taskObject instanceof Task; + return (Task) taskObject; + } + + private static List<Task> convertToTaskList(Object taskListObject) { + assert taskListObject instanceof List<?>; + for (Object userObject : (List<?>) taskListObject) { + assert userObject instanceof Task; + } + + return (List<Task>) taskListObject; + } + + private void compareTasks(Task actual, Task expected) { + assert actual.getStringId().equals(expected.getStringId()); + } + + private void compareTasks(Task actual, List<Task> expected) { + List<String> expectedStringIds = expected.stream().map(Task::getStringId).collect(Collectors.toList()); + + assert expectedStringIds.contains(actual.getStringId()); + } + + private void compareTasks(List<Task> actual, List<Task> expected) { + List<String> actualStringIds = actual.stream().map(Task::getStringId).collect(Collectors.toList()); + List<String> expectedStringIds = expected.stream().map(Task::getStringId).collect(Collectors.toList()); + + assert actualStringIds.containsAll(expectedStringIds); + } + @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml"); @@ -87,9 +113,7 @@ public void testSearchById() { assert count == 1; Object foundTask = searchService.search(query); - - assert foundTask instanceof Task; - assert foundTask.equals(task); + compareTasks(convertToTask(foundTask), task); } @Test @@ -113,13 +137,10 @@ public void testSearchByTransitionId() { assert count == 2; Object foundTask = searchService.search(query); - - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -137,26 +158,16 @@ public void testSearchByTitle() { Task task3 = taskService.findOne(task3Id); String query = String.format("task: title eq '%s'", task.getTitle()); - String queryOther = String.format("task: title eq '%s'", task3.getTitle()); String queryMore = String.format("tasks: title eq '%s'", task.getTitle()); long count = searchService.count(query); - assert count == 2; - - count = searchService.count(queryOther); - assert count == 1; + assert count == 3; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); - - foundTask = searchService.search(queryOther); - assert foundTask instanceof Task; - assert foundTask.equals(task3); + compareTasks(convertToTask(foundTask), List.of(task, task2, task3)); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -169,39 +180,42 @@ public void testSearchByState() { Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); + importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); + importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task3 = taskService.findOne(task3Id); + String task4Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task4 = taskService.findOne(task4Id); + String task5Id = case2.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task5 = taskService.findOne(task5Id); + String task6Id = case3.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task6 = taskService.findOne(task6Id); - importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); - importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); - importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - - String query = String.format("task: state eq %s", "disabled"); - String queryOther = String.format("task: state eq %s", "enabled"); - String queryMore = String.format("tasks: state eq %s", "disabled"); + String query = String.format("task: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); + String queryOther = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "enabled"); + String queryMore = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); long count = searchService.count(query); - assert count == 2; + assert count == 4; count = searchService.count(queryOther); - assert count == 1; + assert count == 5; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2, task4, task5)); - foundTask = searchService.search(queryOther); - assert foundTask instanceof Task; - assert foundTask.equals(task3); + Object foundTasks = searchService.search(queryOther); + compareTasks(convertToTaskList(foundTasks), List.of(task3, task6)); - Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + foundTasks = searchService.search(queryMore); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2, task4, task5)); } @Test @@ -226,9 +240,9 @@ public void testSearchByUserId() { importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); - String query = String.format("case: tasks.t1.userId eq '%s'", user1.getStringId()); - String queryOther = String.format("case: tasks.t1.userId eq '%s'", user2.getStringId()); - String queryMore = String.format("cases: tasks.t1.userId eq '%s'", user1.getStringId()); + String query = String.format("task: userId eq '%s'", user1.getStringId()); + String queryOther = String.format("task: userId eq '%s'", user2.getStringId()); + String queryMore = String.format("tasks: userId eq '%s'", user1.getStringId()); long count = searchService.count(query); assert count == 2; @@ -237,16 +251,13 @@ public void testSearchByUserId() { assert count == 1; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); foundTask = searchService.search(queryOther); - assert foundTask instanceof Task; - assert foundTask.equals(task3); + compareTasks(convertToTask(foundTask), task3); - Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Task>) cases).containsAll(List.of(task, task2)); + Object foundTasks = searchService.search(queryMore); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -262,28 +273,27 @@ public void testSearchByCaseId() { Task task2 = taskService.findOne(task2Id); String task3Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task3 = taskService.findOne(task3Id); + String task4Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Task task4 = taskService.findOne(task4Id); String query = String.format("task: caseId eq '%s'", case1.getStringId()); String queryOther = String.format("task: caseId eq '%s'", case2.getStringId()); String queryMore = String.format("tasks: caseId eq '%s'", case1.getStringId()); long count = searchService.count(query); - assert count == 2; + assert count == 3; count = searchService.count(queryOther); - assert count == 1; + assert count == 3; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); foundTask = searchService.search(queryOther); - assert foundTask instanceof Task; - assert foundTask.equals(task3); + compareTasks(convertToTask(foundTask), List.of(task3, task4)); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -307,22 +317,19 @@ public void testSearchByProcessId() { String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); long count = searchService.count(query); - assert count == 2; + assert count == 6; count = searchService.count(queryOther); - assert count == 1; + assert count == 2; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); foundTask = searchService.search(queryOther); - assert foundTask instanceof Task; - assert foundTask.equals(task3); + compareTasks(convertToTask(foundTask), task3); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -354,16 +361,13 @@ public void testSearchByLastAssign() { assert count == 2; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task); + compareTasks(convertToTask(foundTask), task); foundTask = searchService.search(queryBefore); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } @Test @@ -386,7 +390,7 @@ public void testSearchByLastFinish() { String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); - String query = String.format("task: lastFinish eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); + String query = String.format("task: lastFinish eq %s", SearchUtils.toDateTimeString(task.getLastFinished())); String queryBefore = String.format("task: lastFinish > %s", SearchUtils.toDateTimeString(before)); String queryMore = String.format("tasks: lastFinish > %s", SearchUtils.toDateTimeString(before)); @@ -397,15 +401,12 @@ public void testSearchByLastFinish() { assert count == 2; Object foundTask = searchService.search(query); - assert foundTask instanceof Task; - assert foundTask.equals(task); + compareTasks(convertToTask(foundTask), task); foundTask = searchService.search(queryBefore); - assert foundTask instanceof Task; - assert foundTask.equals(task) || foundTask.equals(task2); + compareTasks(convertToTask(foundTask), List.of(task, task2)); Object foundTasks = searchService.search(queryMore); - assert foundTasks instanceof List; - assert ((List<Task>) foundTasks).containsAll(List.of(task, task2)); + compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java index bb3c79e4ac1..a8e9dcdf27d 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Slf4j @SpringBootTest @@ -49,6 +50,37 @@ private IUser createUser(String name, String surname, String email, String autho return importHelper.createUser(user, authorities, processRoles); } + private static IUser convertToIUser(Object userObject) { + assert userObject instanceof IUser; + return (IUser) userObject; + } + + private static List<IUser> convertToIUserList(Object userListObject) { + assert userListObject instanceof List<?>; + for (Object userObject : (List<?>) userListObject) { + assert userObject instanceof IUser; + } + + return (List<IUser>) userListObject; + } + + private void compareUsers(IUser actual, IUser expected) { + assert actual.getStringId().equals(expected.getStringId()); + } + + private void compareUsers(IUser actual, List<IUser> expected) { + List<String> expectedStringIds = expected.stream().map(IUser::getStringId).collect(Collectors.toList()); + + assert expectedStringIds.contains(actual.getStringId()); + } + + private void compareUsers(List<IUser> actual, List<IUser> expected) { + List<String> actualStringIds = actual.stream().map(IUser::getStringId).collect(Collectors.toList()); + List<String> expectedStringIds = expected.stream().map(IUser::getStringId).collect(Collectors.toList()); + + assert actualStringIds.containsAll(expectedStringIds); + } + @Test public void testSearchById() { IUser user1 = createUser("name1", "surname1", "email1", "user"); @@ -60,9 +92,7 @@ public void testSearchById() { assert count == 1; Object foundUser = searchService.search(query); - - assert foundUser instanceof User; - assert foundUser.equals(user1); + compareUsers(convertToIUser(foundUser), user1); } @Test @@ -76,9 +106,7 @@ public void testSearchByEmail() { assert count == 1; Object foundUser = searchService.search(query); - - assert foundUser instanceof User; - assert foundUser.equals(user1); + compareUsers(convertToIUser(foundUser), user1); } @Test @@ -93,9 +121,7 @@ public void testSearchByName() { assert count == 2; Object foundUsers = searchService.search(query); - - assert foundUsers instanceof List; - assert ((List<User>) foundUsers).containsAll(List.of(user1, user3)); + compareUsers(convertToIUserList(foundUsers), List.of(user1, user3)); } @Test @@ -104,15 +130,13 @@ public void testSearchBySurname() { IUser user2 = createUser("name2", "surname2", "email2", "admin"); IUser user3 = createUser("name1", "surname1", "email3", "user"); - String query = String.format("users: surname eq '%s'", user1.getName()); + String query = String.format("users: surname eq '%s'", user1.getSurname()); long count = searchService.count(query); assert count == 2; Object foundUsers = searchService.search(query); - - assert foundUsers instanceof List; - assert ((List<User>) foundUsers).containsAll(List.of(user1, user3)); + compareUsers(convertToIUserList(foundUsers), List.of(user1, user3)); } } diff --git a/src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java similarity index 91% rename from src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java rename to src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java index a96b084bb89..e5ca2908ef4 100644 --- a/src/test/java/com/netgrif/application/engine/search/MongoDbUtils.java +++ b/src/test/java/com/netgrif/application/engine/search/utils/MongoDbUtils.java @@ -1,4 +1,4 @@ -package com.netgrif.application.engine.search; +package com.netgrif.application.engine.search.utils; import com.querydsl.core.types.Predicate; diff --git a/src/test/resources/petriNets/search/search_test.xml b/src/test/resources/petriNets/search/search_test.xml index c9184d0b1cc..f28e3385c55 100644 --- a/src/test/resources/petriNets/search/search_test.xml +++ b/src/test/resources/petriNets/search/search_test.xml @@ -214,13 +214,6 @@ <id>a3</id> <type>regular</type> <sourceId>p1</sourceId> - <destinationId>search_test_t1</destinationId> - <multiplicity>1</multiplicity> - </arc> - <arc> - <id>a4</id> - <type>regular</type> - <sourceId>p1</sourceId> <destinationId>search_test_t2</destinationId> <multiplicity>1</multiplicity> </arc> From 863990ff9badbfa6aa583806106cee26411c6b01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Fri, 31 Jan 2025 09:51:06 +0100 Subject: [PATCH 14/27] [NAE-1997] Query language - update grammar (parenthesis) - add elastic query tests --- .../engine/search/QueryLangEvaluator.java | 50 +- .../engine/search/antlr4/QueryLang.g4 | 16 +- .../engine/search/QueryLangTest.java | 483 +++++++++++++++++- 3 files changed, 530 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 6aeb7ed415e..8be3605f9bc 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -99,7 +99,7 @@ private void processAndExpression(List<ParseTree> children, ParseTree current) { setElasticQuery(current, elasticQuery.isBlank() ? null : elasticQuery); } - private void processConditionGroup(ParseTree child, ParseTree current, Boolean not) { + private void processConditionGroup(ParseTree child, ParseTree current, Boolean not, Boolean parenthesis) { Predicate predicate = getMongoQuery(child); String elasticQuery = getElasticQuery(child); @@ -108,7 +108,13 @@ private void processConditionGroup(ParseTree child, ParseTree current, Boolean n } if (elasticQuery != null) { - elasticQuery = not ? "NOT (" + elasticQuery + ")" : "(" + elasticQuery + ")"; + if (parenthesis) { + elasticQuery = "(" + elasticQuery + ")"; + } + + if (not) { + elasticQuery = "NOT " + elasticQuery; + } } setMongoQuery(current, predicate); @@ -204,8 +210,13 @@ public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext } @Override - public void exitProcessConditionGroup(QueryLangParser.ProcessConditionGroupContext ctx) { - processConditionGroup(ctx.processCondition() != null ? ctx.processCondition() : ctx.processConditions(), ctx, ctx.NOT() != null); + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + processConditionGroup(ctx.processCondition(), ctx, false, false); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.processConditions(), ctx, ctx.NOT() != null, true); } @Override @@ -237,8 +248,13 @@ public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) } @Override - public void exitCaseConditionGroup(QueryLangParser.CaseConditionGroupContext ctx) { - processConditionGroup(ctx.caseCondition() != null ? ctx.caseCondition() : ctx.caseConditions(), ctx, ctx.NOT() != null); + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + processConditionGroup(ctx.caseCondition(), ctx, false, false); + } + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.caseConditions(), ctx, ctx.NOT() != null, true); } @Override @@ -270,8 +286,13 @@ public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) } @Override - public void exitTaskConditionGroup(QueryLangParser.TaskConditionGroupContext ctx) { - processConditionGroup(ctx.taskCondition() != null ? ctx.taskCondition() : ctx.taskConditions(), ctx, ctx.NOT() != null); + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + processConditionGroup(ctx.taskCondition(), ctx, false, false); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.taskConditions(), ctx, ctx.NOT() != null, true); } @Override @@ -303,8 +324,13 @@ public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) } @Override - public void exitUserConditionGroup(QueryLangParser.UserConditionGroupContext ctx) { - processConditionGroup(ctx.userCondition() != null ? ctx.userCondition() : ctx.userConditions(), ctx, ctx.NOT() != null); + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + processConditionGroup(ctx.userCondition(), ctx, false, false); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + processConditionGroup(ctx.userConditions(), ctx, ctx.NOT() != null, true); } @Override @@ -616,7 +642,7 @@ public void exitDataDate(QueryLangParser.DataDateContext ctx) { LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, Timestamp.valueOf(localDateTime).toString())); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); } @Override @@ -627,7 +653,7 @@ public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, Timestamp.valueOf(localDateTime).toString())); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); } @Override diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 0fc424ea8e5..cc23ba1dec4 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -9,25 +9,33 @@ query: resource=(PROCESS | PROCESSES) delimeter processConditions EOF # processQ processConditions: processOrExpression ; processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; -processConditionGroup: processCondition | (NOT SPACE)? '(' SPACE? processConditions SPACE? ')' SPACE? ; +processConditionGroup: processCondition # processConditionGroupBasic + | (NOT SPACE)? '(' SPACE? processConditions SPACE? ')' SPACE? # processConditionGroupParenthesis + ; processCondition: (NOT SPACE)? processComparisons SPACE? ; caseConditions: caseOrExpression ; caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; -caseConditionGroup: caseCondition | (NOT SPACE)? '(' SPACE? caseConditions SPACE? ')' SPACE? ; +caseConditionGroup: caseCondition # caseConditionGroupBasic + | (NOT SPACE)? '(' SPACE? caseConditions SPACE? ')' SPACE? # caseConditionGroupParenthesis + ; caseCondition: (NOT SPACE)? caseComparisons SPACE? ; taskConditions: taskOrExpression ; taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; -taskConditionGroup: taskCondition | (NOT SPACE)? '(' SPACE? taskConditions SPACE? ')' SPACE? ; +taskConditionGroup: taskCondition # taskConditionGroupBasic + | (NOT SPACE)? '(' SPACE? taskConditions SPACE? ')' SPACE? # taskConditionGroupParenthesis + ; taskCondition: (NOT SPACE)? taskComparisons SPACE? ; userConditions: userOrExpression ; userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; -userConditionGroup: userCondition | (NOT SPACE)? '(' SPACE? userConditions SPACE? ')' SPACE? ; +userConditionGroup: userCondition # userConditionGroupBasic + | (NOT SPACE)? '(' SPACE? userConditions SPACE? ')' SPACE? # userConditionGroupParenthesis + ; userCondition: (NOT SPACE)? userComparisons SPACE? ; // delimeter diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index d0c792f5736..bd86c725243 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -20,6 +20,7 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.sql.Timestamp; import java.time.LocalDateTime; import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; @@ -34,9 +35,6 @@ public class QueryLangTest { @Autowired MongoOperations mongoOperations; - // todo NAE-1997:: simple queries logical elastic string query comparison - // todo NAE-1997:: complex queries logical elastic string query comparison - @Test public void testSimpleMongodbProcessQuery() { MongoDbUtils<PetriNet> mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); @@ -160,6 +158,8 @@ public void testComplexMongodbProcessQuery() { actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test")); + compareMongoQueries(mongoDbUtils, actual, expected); + // or not comparison actual = evaluateQuery(String.format("process: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test").not()); @@ -666,6 +666,483 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); } + @Test + public void testSimpleElasticProcessQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("process: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // identifier comparison + actual = evaluateQuery("process: identifier eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: identifier contains 'test'").getFullElasticQuery(); + assert actual == null; + + // version comparison + actual = evaluateQuery("process: version eq 1.1.1").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: version lt 1.1.1").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("process: version lte 1.1.1").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: version gt 1.1.1").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: version gte 1.1.1").getFullElasticQuery(); + assert actual == null; + + + // title comparison + actual = evaluateQuery("process: title eq 'test'").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: title contains 'test'").getFullElasticQuery(); + assert actual == null; + + + // creationDate comparison + actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + + actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticProcessQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("process: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("process: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("process: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("process: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("process: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + + // nested parenthesis comparison + actual = evaluateQuery(String.format("process: id eq '%s' and (title eq 'test' or (title eq 'test1' and identifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testSimpleElasticCaseQuery() { + LocalDateTime localDateTime = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + + // id comparison + String actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String expected = String.format("stringId:%s", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // processId comparison + actual = evaluateQuery("case: processId eq 'test'").getFullElasticQuery(); + expected = "processId:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: processId contains 'test'").getFullElasticQuery(); + expected = "processId:*test*"; + assert expected.equals(actual); + + // processIdentifier comparison + actual = evaluateQuery("case: processIdentifier eq 'test'").getFullElasticQuery(); + expected = "processIdentifier:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: processIdentifier contains 'test'").getFullElasticQuery(); + expected = "processIdentifier:*test*"; + assert expected.equals(actual); + + // title comparison + actual = evaluateQuery("case: title eq 'test'").getFullElasticQuery(); + expected = "title:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: title contains 'test'").getFullElasticQuery(); + expected = "title:*test*"; + assert expected.equals(actual); + + // creationDate comparison + actual = evaluateQuery("case: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("creationDateSortable:%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("creationDateSortable:<%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("creationDateSortable:<=%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("creationDateSortable:>%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("creationDateSortable:>=%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + // author comparison + actual = evaluateQuery("case: author eq 'test'").getFullElasticQuery(); + expected = "author:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: author contains 'test'").getFullElasticQuery(); + expected = "author:*test*"; + assert expected.equals(actual); + + // places comparison + actual = evaluateQuery("case: places.p1.marking eq 1").getFullElasticQuery(); + expected = "places.p1.marking:1"; + assert expected.equals(actual); + + // task state comparison + actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullElasticQuery(); + expected = String.format("tasks.t1.state:%s", State.ENABLED); + assert expected.equals(actual); + + actual = evaluateQuery("case: tasks.t1.state eq disabled").getFullElasticQuery(); + expected = String.format("tasks.t1.state:%s", State.DISABLED); + assert expected.equals(actual); + + // task userId comparison + actual = evaluateQuery("case: tasks.t1.userId eq 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: tasks.t1.userId contains 'test'").getFullElasticQuery(); + expected = "tasks.t1.userId:*test*"; + assert expected.equals(actual); + + // data value comparison + actual = evaluateQuery("case: data.field1.value eq 'test'").getFullElasticQuery(); + expected = "dataSet.field1.textValue:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value contains 'test'").getFullElasticQuery(); + expected = "dataSet.field1.textValue:*test*"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value eq 1").getFullElasticQuery(); + expected = "dataSet.field1.numberValue:1"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value lt 1").getFullElasticQuery(); + expected = "dataSet.field1.numberValue:<1"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value lte 1").getFullElasticQuery(); + expected = "dataSet.field1.numberValue:<=1"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value gt 1").getFullElasticQuery(); + expected = "dataSet.field1.numberValue:>1"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value gte 1").getFullElasticQuery(); + expected = "dataSet.field1.numberValue:>=1"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("dataSet.field1.timestampValue:%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("dataSet.field1.timestampValue:<%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("dataSet.field1.timestampValue:<=%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("dataSet.field1.timestampValue:>%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullElasticQuery(); + expected = String.format("dataSet.field1.timestampValue:>=%s", Timestamp.valueOf(localDateTime).getTime()); + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:true"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.value eq false").getFullElasticQuery(); + expected = "dataSet.field1.booleanValue:false"; + assert expected.equals(actual); + + // data options comparison + actual = evaluateQuery("case: data.field1.options eq 'test'").getFullElasticQuery(); + expected = "dataSet.field1.options:test"; + assert expected.equals(actual); + + actual = evaluateQuery("case: data.field1.options contains 'test'").getFullElasticQuery(); + expected = "dataSet.field1.options:*test*"; + assert expected.equals(actual); + } + + @Test + public void testComplexElasticCaseQuery() { + // not comparison + String actual = evaluateQuery(String.format("case: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // and comparison + actual = evaluateQuery(String.format("case: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // and not comparison + actual = evaluateQuery(String.format("case: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // or comparison + actual = evaluateQuery(String.format("case: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // or not comparison + actual = evaluateQuery(String.format("case: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR title:test1)", GENERIC_OBJECT_ID); + assert expected.equals(actual); + + // nested parenthesis comparison + actual = evaluateQuery(String.format("case: id eq '%s' and (title eq 'test' or (title eq 'test1' and processIdentifier eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:%s AND (title:test OR (title:test1 AND processIdentifier:test))", GENERIC_OBJECT_ID); + assert expected.equals(actual); + } + + @Test + public void testSimpleElasticTaskQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("task: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // transitionId comparison + actual = evaluateQuery("task: transitionId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: transitionId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // title comparison + actual = evaluateQuery("task: title eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: title contains 'test'").getFullElasticQuery(); + assert actual == null; + + // state comparison + actual = evaluateQuery("task: state eq enabled").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: state eq disabled").getFullElasticQuery(); + assert actual == null; + + // userId comparison + actual = evaluateQuery("task: userId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: userId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // caseId comparison + actual = evaluateQuery("task: caseId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: caseId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // processId comparison + actual = evaluateQuery("task: processId eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: processId contains 'test'").getFullElasticQuery(); + assert actual == null; + + // lastAssign comparison + actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + // lastFinish comparison + actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticTaskQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("task: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("task: id eq '%s' and title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("task: id eq '%s' or title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("task: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("task: id eq '%s' and not (title eq 'test' or title eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // nested parenthesis comparison + actual = evaluateQuery(String.format("task: id eq '%s' and (title eq 'test' or (title eq 'test1' and processId eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testSimpleElasticUserQuery() { + // elastic query should be always null + // id comparison + String actual = evaluateQuery(String.format("user: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // name comparison + actual = evaluateQuery("user: name eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: name contains 'test'").getFullElasticQuery(); + assert actual == null; + + // surname comparison + actual = evaluateQuery("user: surname eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: surname contains 'test'").getFullElasticQuery(); + assert actual == null; + + // email comparison + actual = evaluateQuery("user: email eq 'test'").getFullElasticQuery(); + assert actual == null; + + actual = evaluateQuery("user: email contains 'test'").getFullElasticQuery(); + assert actual == null; + } + + @Test + public void testComplexElasticUserQuery() { + // elastic query should be always null + // not comparison + String actual = evaluateQuery(String.format("user: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and comparison + actual = evaluateQuery(String.format("user: id eq '%s' and email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // and not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or comparison + actual = evaluateQuery(String.format("user: id eq '%s' or email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // or not comparison + actual = evaluateQuery(String.format("user: id eq '%s' or not email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // parenthesis not comparison + actual = evaluateQuery(String.format("user: id eq '%s' and not (email eq 'test' or email eq 'test1')", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + + // nested parenthesis comparison + actual = evaluateQuery(String.format("user: id eq '%s' and (email eq 'test' or (email eq 'test1' and name eq 'test'))", GENERIC_OBJECT_ID)).getFullElasticQuery(); + assert actual == null; + } + @Test public void testProcessQueriesFail() { // using case, task, user attributes From f1d5cfba5df31694a77c7c5ebc26aab7deed2a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Mon, 3 Feb 2025 13:30:48 +0100 Subject: [PATCH 15/27] [NAE-1997] Query language - update grammar (not operator) - fix some case search tests --- .../engine/search/QueryLangEvaluator.java | 132 +++++--- .../engine/search/SearchService.java | 8 +- .../engine/search/SearchUtils.java | 121 +++++-- .../engine/search/antlr4/QueryLang.g4 | 39 +-- .../engine/search/QueryLangTest.java | 48 +-- .../engine/search/SearchCaseTest.java | 307 +++++++++--------- .../engine/search/SearchTaskTest.java | 4 +- 7 files changed, 370 insertions(+), 289 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 8be3605f9bc..e7d8e6c7a71 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -14,6 +14,7 @@ import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; import lombok.Getter; +import lombok.Setter; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeProperty; @@ -38,6 +39,9 @@ public class QueryLangEvaluator extends QueryLangBaseListener { @Getter private Boolean multiple; @Getter + @Setter + private Boolean searchWithElastic = false; + @Getter private Predicate fullMongoQuery; @Getter private String fullElasticQuery; @@ -121,19 +125,6 @@ private void processConditionGroup(ParseTree child, ParseTree current, Boolean n setElasticQuery(current, elasticQuery); } - private void processCondition(ParseTree child, ParseTree current, Boolean not) { - Predicate predicate = getMongoQuery(child); - String elasticQuery = getElasticQuery(child); - - if (not) { - predicate = predicate != null ? predicate.not() : null; - elasticQuery = elasticQuery != null ? "NOT " + elasticQuery : null; - } - - setMongoQuery(current, predicate); - setElasticQuery(current, elasticQuery); - } - @Override public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { type = QueryType.PROCESS; @@ -221,7 +212,7 @@ public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditio @Override public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { - processCondition(ctx.processComparisons(), ctx, ctx.NOT() != null); + processBasicExpression(ctx.processComparisons(), ctx); } @Override @@ -259,7 +250,7 @@ public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroup @Override public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { - processCondition(ctx.caseComparisons(), ctx, ctx.NOT() != null); + processBasicExpression(ctx.caseComparisons(), ctx); } @Override @@ -297,7 +288,7 @@ public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroup @Override public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { - processCondition(ctx.taskComparisons(), ctx, ctx.NOT() != null); + processBasicExpression(ctx.taskComparisons(), ctx); } @Override @@ -335,7 +326,7 @@ public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroup @Override public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { - processCondition(ctx.userComparisons(), ctx, ctx.NOT() != null); + processBasicExpression(ctx.userComparisons(), ctx); } @Override @@ -362,6 +353,7 @@ public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { QObjectId qObjectId; Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; checkOp(ComparisonType.ID, op); ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); @@ -371,7 +363,7 @@ public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { break; case CASE: qObjectId = QCase.case$.id; - setElasticQuery(ctx, buildElasticQuery("stringId", op, objectId.toString())); + setElasticQuery(ctx, buildElasticQuery("stringId", op, objectId.toString(), not)); break; case TASK: qObjectId = QTask.task.id; @@ -383,13 +375,14 @@ public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId)); + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId, not)); } @Override public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { StringPath stringPath; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); switch (type) { @@ -398,7 +391,7 @@ public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { break; case CASE: stringPath = QCase.case$.title; - setElasticQuery(ctx, buildElasticQuery("title", op, string)); + setElasticQuery(ctx, buildElasticQuery("title", op, string, not)); break; case TASK: stringPath = QTask.task.title.defaultValue; @@ -407,30 +400,33 @@ public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { StringPath stringPath = QPetriNet.petriNet.identifier; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitVersionComparison(QueryLangParser.VersionComparisonContext ctx) { Token op = ctx.op; + boolean not = ctx.NOT() != null; String versionString = ctx.VERSION_NUMBER().getText(); - setMongoQuery(ctx, buildVersionPredicate(op, versionString)); + setMongoQuery(ctx, buildVersionPredicate(op, versionString, not)); } @Override public void exitCdDate(QueryLangParser.CdDateContext ctx) { DateTimePath<LocalDateTime> dateTimePath; Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); switch (type) { @@ -439,19 +435,20 @@ public void exitCdDate(QueryLangParser.CdDateContext ctx) { break; case CASE: dateTimePath = QCase.case$.creationDate; - setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { DateTimePath<LocalDateTime> dateTimePath; Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); switch (type) { @@ -460,25 +457,26 @@ public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { break; case CASE: dateTimePath = QCase.case$.creationDate; - setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { StringPath stringPath; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); switch (type) { case CASE: stringPath = QCase.case$.petriNetId; - setElasticQuery(ctx, buildElasticQuery("processId", op, string)); + setElasticQuery(ctx, buildElasticQuery("processId", op, string, not)); break; case TASK: stringPath = QTask.task.processId; @@ -487,36 +485,39 @@ public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext c throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitProcessIdentifierComparison(QueryLangParser.ProcessIdentifierComparisonContext ctx) { StringPath stringPath = QCase.case$.processIdentifier; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); - setElasticQuery(ctx, buildElasticQuery("processIdentifier", op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setElasticQuery(ctx, buildElasticQuery("processIdentifier", op, string, not)); } @Override public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { StringPath stringPath = QCase.case$.author.id; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); - setElasticQuery(ctx, buildElasticQuery("author", op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setElasticQuery(ctx, buildElasticQuery("author", op, string, not)); } @Override public void exitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { StringPath stringPath = QTask.task.transitionId; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override @@ -535,81 +536,90 @@ public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { public void exitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { StringPath stringPath = QTask.task.userId; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { StringPath stringPath = QTask.task.caseId; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitLaDate(QueryLangParser.LaDateContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitLaDateTime(QueryLangParser.LaDateTimeContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitLfDate(QueryLangParser.LfDateContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; Token op = ctx.dateComparison().op; + boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitLfDateTime(QueryLangParser.LfDateTimeContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; Token op = ctx.dateTimeComparison().op; + boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); } @Override public void exitNameComparison(QueryLangParser.NameComparisonContext ctx) { StringPath stringPath = QUser.user.name; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { StringPath stringPath = QUser.user.surname; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override public void exitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { StringPath stringPath = QUser.user.email; Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override @@ -617,10 +627,12 @@ public void exitDataString(QueryLangParser.DataStringContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op, string)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op, string, not)); + this.searchWithElastic = true; } @Override @@ -628,10 +640,12 @@ public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; String number = ctx.numberComparison().NUMBER().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op, number)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op, number, not)); + this.searchWithElastic = true; } @Override @@ -639,10 +653,12 @@ public void exitDataDate(QueryLangParser.DataDateContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.dateComparison().op; checkOp(ComparisonType.DATE, op); + boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; } @Override @@ -650,10 +666,12 @@ public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.dateTimeComparison().op; checkOp(ComparisonType.DATETIME, op); + boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()))); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; } @Override @@ -661,10 +679,12 @@ public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); Token op = ctx.booleanComparison().op; checkOp(ComparisonType.BOOLEAN, op); + boolean not = ctx.booleanComparison().NOT() != null; String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, booleanValue)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, booleanValue, not)); + this.searchWithElastic = true; } @Override @@ -672,10 +692,12 @@ public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonCont String fieldId = ctx.dataOptions().fieldId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op, string)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op, string, not)); + this.searchWithElastic = true; } @Override @@ -683,10 +705,12 @@ public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { String placeId = ctx.places().placeId.getText(); Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); + boolean not = ctx.numberComparison().NOT() != null; String numberValue = ctx.numberComparison().NUMBER().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue)); + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue, not)); + this.searchWithElastic = true; } @Override @@ -694,10 +718,12 @@ public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext String taskId = ctx.tasksState().taskId.getText(); Token op = ctx.op; checkOp(ComparisonType.STRING, op); + boolean not = ctx.NOT() != null; State state = ctx.state.getType() == QueryLangParser.ENABLED ? State.ENABLED : State.DISABLED; setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op, state.toString())); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op, state.toString(), not)); + this.searchWithElastic = true; } @Override @@ -705,9 +731,11 @@ public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonConte String taskId = ctx.tasksUserId().taskId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); + boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op, string)); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op, string, not)); + this.searchWithElastic = true; } } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index e77f520a0a2..7d7418d18ec 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -28,6 +28,8 @@ @RequiredArgsConstructor public class SearchService implements ISearchService { + public static final int DEFAULT_ELASTIC_PAGE_SIZE = 100000; + private final PetriNetRepository petriNetRepository; private final IElasticCaseService elasticCaseService; @@ -61,7 +63,7 @@ private List<Case> findCasesElastic(String elasticQuery) { return elasticCaseService.search( List.of(caseSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), - new FullPageRequest(), LocaleContextHolder.getLocale(), + PageRequest.of(0, DEFAULT_ELASTIC_PAGE_SIZE), LocaleContextHolder.getLocale(), false ).getContent(); } @@ -80,7 +82,7 @@ public Object search(String input) { return petriNetRepository.findAll(predicate, PageRequest.of(0, 1)) .getContent().stream().findFirst().orElse(null); case CASE: - if (predicate != null) { + if (!evaluator.getSearchWithElastic()) { if (evaluator.getMultiple()) { return workflowService.searchAll(predicate).getContent(); } @@ -114,7 +116,7 @@ public Long count(String input) { case PROCESS: return petriNetRepository.count(predicate); case CASE: - if (predicate != null) { + if (!evaluator.getSearchWithElastic()) { return caseRepository.count(predicate); } return countCasesElastic(elasticQuery); diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java index d2983460186..48c49f4c09b 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchUtils.java @@ -18,6 +18,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.List; @@ -44,7 +45,7 @@ public static String toDateString(LocalDateTime localDateTime) { } public static String toDateTimeString(LocalDate localDate) { - return localDate.atStartOfDay().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); + return localDate.atTime(12, 0, 0).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); } public static String toDateTimeString(LocalDateTime localDateTime) { @@ -56,7 +57,7 @@ public static LocalDateTime toDateTime(String dateTimeString) { return LocalDateTime.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } catch (DateTimeParseException ignored) { try { - return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atStartOfDay(); + return LocalDate.parse(dateTimeString, DateTimeFormatter.ISO_LOCAL_DATE).atTime(12, 0, 0); } catch (DateTimeParseException e) { throw new IllegalArgumentException("Invalid date/datetime format"); } @@ -113,26 +114,40 @@ public static void checkOp(ComparisonType type, Token op) { } } - public static Predicate buildObjectIdPredicate(QObjectId qObjectId, Token op, ObjectId objectId) { - if (op.getType() == QueryLangParser.EQ) { - return qObjectId.eq(objectId); + public static Predicate buildObjectIdPredicate(QObjectId qObjectId, Token op, ObjectId objectId, boolean not) { + if (op.getType() != QueryLangParser.EQ) { + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for id comparison"); } - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for id comparison"); + Predicate predicate = qObjectId.eq(objectId); + if (not) { + predicate = predicate.not(); + } + return predicate; } - public static Predicate buildStringPredicate(StringPath stringPath, Token op, String string) { + public static Predicate buildStringPredicate(StringPath stringPath, Token op, String string, boolean not) { + Predicate predicate = null; switch (op.getType()) { case QueryLangParser.EQ: - return stringPath.eq(string); + predicate = stringPath.eq(string); + break; case QueryLangParser.CONTAINS: - return stringPath.contains(string); + predicate = stringPath.contains(string); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for string comparison"); } - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for string comparison"); + if (not) { + predicate = predicate.not(); + } + return predicate; } - public static Predicate buildVersionPredicate(Token op, String versionString) { + public static Predicate buildVersionPredicate(Token op, String versionString, boolean not) { String[] versionNumber = versionString.split("\\."); long major = Long.parseLong(versionNumber[0]); long minor = Long.parseLong(versionNumber[1]); @@ -140,63 +155,105 @@ public static Predicate buildVersionPredicate(Token op, String versionString) { QVersion qVersion = QPetriNet.petriNet.version; + Predicate predicate = null; switch (op.getType()) { case QueryLangParser.EQ: - return qVersion.eq(new Version(major, minor, patch)); + predicate = qVersion.eq(new Version(major, minor, patch)); + break; case QueryLangParser.GT: - return qVersion.major.gt(major) + predicate = qVersion.major.gt(major) .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); + break; case QueryLangParser.GTE: - return qVersion.major.gt(major) + predicate = qVersion.major.gt(major) .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))) .or(qVersion.eq(new Version(major, minor, patch))); + break; case QueryLangParser.LT: - return qVersion.major.lt(major) + predicate = qVersion.major.lt(major) .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); + break; case QueryLangParser.LTE: - return qVersion.major.lt(major) + predicate = qVersion.major.lt(major) .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) .or(qVersion.eq(new Version(major, minor, patch))); + break; + } + + if (predicate == null) { + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for version comparison"); + } + + if (not) { + predicate = predicate.not(); } - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for version comparison"); + return predicate; } - public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateTimePath, Token op, LocalDateTime localDateTime) { + public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateTimePath, Token op, LocalDateTime localDateTime, boolean not) { + Predicate predicate = null; switch (op.getType()) { case QueryLangParser.EQ: - return dateTimePath.eq(localDateTime); + predicate = dateTimePath.eq(localDateTime); + break; case QueryLangParser.LT: - return dateTimePath.lt(localDateTime); + predicate = dateTimePath.lt(localDateTime); + break; case QueryLangParser.LTE: - return dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); + predicate = dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); + break; case QueryLangParser.GT: - return dateTimePath.gt(localDateTime); + predicate = dateTimePath.gt(localDateTime); + break; case QueryLangParser.GTE: - return dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); + predicate = dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); + break; } - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); + if (predicate == null) { + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); + } + + if (not) { + predicate = predicate.not(); + } + return predicate; } - public static String buildElasticQuery(String attribute, Token op, String value) { + public static String buildElasticQuery(String attribute, Token op, String value, boolean not) { + String query = null; switch (op.getType()) { case QueryLangParser.EQ: - return attribute + ":" + value; + query = attribute + ":" + value; + break; case QueryLangParser.LT: - return attribute + ":<" + value; + query = attribute + ":<" + value; + break; case QueryLangParser.LTE: - return attribute + ":<=" + value; + query = attribute + ":<=" + value; + break; case QueryLangParser.GT: - return attribute + ":>" + value; + query = attribute + ":>" + value; + break; case QueryLangParser.GTE: - return attribute + ":>=" + value; + query = attribute + ":>=" + value; + break; case QueryLangParser.CONTAINS: - return attribute + ":*" + value + "*"; + query = attribute + ":*" + value + "*"; + break; + } + + if (query == null) { + throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for elastic comparison"); + } + + if (not) { + query = "NOT " + query; } - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for elastic comparison"); + return query; } } diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index cc23ba1dec4..49f52ae81e0 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -1,3 +1,4 @@ +// todo NAE-1997: generate this with plugin grammar QueryLang; query: resource=(PROCESS | PROCESSES) delimeter processConditions EOF # processQuery @@ -10,33 +11,33 @@ processConditions: processOrExpression ; processOrExpression: processAndExpression (SPACE OR SPACE processAndExpression)* ; processAndExpression: processConditionGroup (SPACE AND SPACE processConditionGroup)* ; processConditionGroup: processCondition # processConditionGroupBasic - | (NOT SPACE)? '(' SPACE? processConditions SPACE? ')' SPACE? # processConditionGroupParenthesis + | (NOT SPACE?)? '(' SPACE? processConditions SPACE? ')' SPACE? # processConditionGroupParenthesis ; -processCondition: (NOT SPACE)? processComparisons SPACE? ; +processCondition: processComparisons SPACE? ; caseConditions: caseOrExpression ; caseOrExpression: caseAndExpression (SPACE OR SPACE caseAndExpression)* ; caseAndExpression: caseConditionGroup (SPACE AND SPACE caseConditionGroup)* ; caseConditionGroup: caseCondition # caseConditionGroupBasic - | (NOT SPACE)? '(' SPACE? caseConditions SPACE? ')' SPACE? # caseConditionGroupParenthesis + | (NOT SPACE?)? '(' SPACE? caseConditions SPACE? ')' SPACE? # caseConditionGroupParenthesis ; -caseCondition: (NOT SPACE)? caseComparisons SPACE? ; +caseCondition: caseComparisons SPACE? ; taskConditions: taskOrExpression ; taskOrExpression: taskAndExpression (SPACE OR SPACE taskAndExpression)* ; taskAndExpression: taskConditionGroup (SPACE AND SPACE taskConditionGroup)* ; taskConditionGroup: taskCondition # taskConditionGroupBasic - | (NOT SPACE)? '(' SPACE? taskConditions SPACE? ')' SPACE? # taskConditionGroupParenthesis + | (NOT SPACE?)? '(' SPACE? taskConditions SPACE? ')' SPACE? # taskConditionGroupParenthesis ; -taskCondition: (NOT SPACE)? taskComparisons SPACE? ; +taskCondition: taskComparisons SPACE? ; userConditions: userOrExpression ; userOrExpression: userAndExpression (SPACE OR SPACE userAndExpression)* ; userAndExpression: userConditionGroup (SPACE AND SPACE userConditionGroup)* ; userConditionGroup: userCondition # userConditionGroupBasic - | (NOT SPACE)? '(' SPACE? userConditions SPACE? ')' SPACE? # userConditionGroupParenthesis + | (NOT SPACE?)? '(' SPACE? userConditions SPACE? ')' SPACE? # userConditionGroupParenthesis ; -userCondition: (NOT SPACE)? userComparisons SPACE? ; +userCondition: userComparisons SPACE? ; // delimeter delimeter: WHERE_DELIMETER | COLON_DELIMETER ; @@ -85,7 +86,7 @@ userComparisons: idComparison idComparison: ID SPACE objectIdComparison ; titleComparison: TITLE SPACE stringComparison ; identifierComparison: IDENTIFIER SPACE stringComparison ; -versionComparison: VERSION SPACE op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; +versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; creationDateComparison: CREATION_DATE SPACE dateComparison # cdDate | CREATION_DATE SPACE dateTimeComparison # cdDateTime ; @@ -113,16 +114,16 @@ dataValueComparison: dataValue SPACE stringComparison # dataString ; dataOptionsComparison: dataOptions SPACE stringComparison ; placesComparison: places SPACE numberComparison ; -tasksStateComparison: tasksState SPACE op=EQ SPACE state=(ENABLED | DISABLED) ; +tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; tasksUserIdComparison: tasksUserId SPACE stringComparison ; // basic comparisons -objectIdComparison: op=EQ SPACE STRING ; -stringComparison: op=(EQ | CONTAINS) SPACE STRING ; -numberComparison: op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; -dateComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; -dateTimeComparison: op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; -booleanComparison: op=EQ SPACE BOOLEAN ; +objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; +stringComparison: (NOT SPACE?)? op=(EQ | CONTAINS) SPACE STRING ; +numberComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; +dateComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; +dateTimeComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; +booleanComparison: (NOT SPACE?)? op=EQ SPACE BOOLEAN ; // special attribute rules dataValue: DATA '.' fieldId=JAVA_ID '.'VALUE ; @@ -181,10 +182,10 @@ DISABLED: D I S A B L E D ; // basic types LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; -STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; +STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? NUMBER: DIGIT+ ('.' DIGIT+)? ; -DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00 -DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 +DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00.055 // todo NAE-1997: format +DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 // todo NAE-1997: format BOOLEAN: T R U E | F A L S E ; VERSION_NUMBER: DIGIT+ '.' DIGIT+ '.' DIGIT+ ; JAVA_ID: [a-zA-Z$_] [a-zA-Z0-9$_]* ; diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index bd86c725243..622b89edbec 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -137,7 +137,7 @@ public void testComplexMongodbProcessQuery() { MongoDbUtils<PetriNet> mongoDbUtils = new MongoDbUtils<>(mongoOperations, PetriNet.class); // not comparison - Predicate actual = evaluateQuery(String.format("process: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); Predicate expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -149,7 +149,7 @@ public void testComplexMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // and not comparison - actual = evaluateQuery(String.format("process: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).and(QPetriNet.petriNet.title.defaultValue.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -161,7 +161,7 @@ public void testComplexMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // or not comparison - actual = evaluateQuery(String.format("process: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QPetriNet.petriNet.id.eq(GENERIC_OBJECT_ID).or(QPetriNet.petriNet.title.defaultValue.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -335,7 +335,7 @@ public void testComplexMongodbCaseQuery() { MongoDbUtils<Case> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Case.class); // not comparison - Predicate actual = evaluateQuery(String.format("case: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); Predicate expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -347,7 +347,7 @@ public void testComplexMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // and not comparison - actual = evaluateQuery(String.format("case: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).and(QCase.case$.title.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -359,7 +359,7 @@ public void testComplexMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // or not comparison - actual = evaluateQuery(String.format("case: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QCase.case$.id.eq(GENERIC_OBJECT_ID).or(QCase.case$.title.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -517,7 +517,7 @@ public void testComplexMongodbTaskQuery() { MongoDbUtils<Task> mongoDbUtils = new MongoDbUtils<>(mongoOperations, Task.class); // not comparison - Predicate actual = evaluateQuery(String.format("task: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); Predicate expected = QTask.task.id.eq(GENERIC_OBJECT_ID).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -529,7 +529,7 @@ public void testComplexMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // and not comparison - actual = evaluateQuery(String.format("task: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QTask.task.id.eq(GENERIC_OBJECT_ID).and(QTask.task.title.defaultValue.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -541,7 +541,7 @@ public void testComplexMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // or not comparison - actual = evaluateQuery(String.format("task: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QTask.task.id.eq(GENERIC_OBJECT_ID).or(QTask.task.title.defaultValue.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -616,7 +616,7 @@ public void testComplexMongodbUserQuery() { MongoDbUtils<User> mongoDbUtils = new MongoDbUtils<>(mongoOperations, User.class); // not comparison - Predicate actual = evaluateQuery(String.format("user: not id eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + Predicate actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); Predicate expected = QUser.user.id.eq(GENERIC_OBJECT_ID).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -628,7 +628,7 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // and not comparison - actual = evaluateQuery(String.format("user: id eq '%s' and not email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QUser.user.id.eq(GENERIC_OBJECT_ID).and(QUser.user.email.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -640,7 +640,7 @@ public void testComplexMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // or not comparison - actual = evaluateQuery(String.format("user: id eq '%s' or not email eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QUser.user.id.eq(GENERIC_OBJECT_ID).or(QUser.user.email.eq("test").not()); compareMongoQueries(mongoDbUtils, actual, expected); @@ -733,7 +733,7 @@ public void testSimpleElasticProcessQuery() { public void testComplexElasticProcessQuery() { // elastic query should be always null // not comparison - String actual = evaluateQuery(String.format("process: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String actual = evaluateQuery(String.format("process: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // and comparison @@ -741,7 +741,7 @@ public void testComplexElasticProcessQuery() { assert actual == null; // and not comparison - actual = evaluateQuery(String.format("process: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("process: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // or comparison @@ -749,7 +749,7 @@ public void testComplexElasticProcessQuery() { assert actual == null; // or not comparison - actual = evaluateQuery(String.format("process: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("process: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // parenthesis comparison @@ -925,7 +925,7 @@ public void testSimpleElasticCaseQuery() { @Test public void testComplexElasticCaseQuery() { // not comparison - String actual = evaluateQuery(String.format("case: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String actual = evaluateQuery(String.format("case: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); String expected = String.format("NOT stringId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); @@ -935,7 +935,7 @@ public void testComplexElasticCaseQuery() { assert expected.equals(actual); // and not comparison - actual = evaluateQuery(String.format("case: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("case: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("stringId:%s AND NOT title:test", GENERIC_OBJECT_ID); assert expected.equals(actual); @@ -945,7 +945,7 @@ public void testComplexElasticCaseQuery() { assert expected.equals(actual); // or not comparison - actual = evaluateQuery(String.format("case: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("case: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("stringId:%s OR NOT title:test", GENERIC_OBJECT_ID); assert expected.equals(actual); @@ -1046,7 +1046,7 @@ public void testSimpleElasticTaskQuery() { public void testComplexElasticTaskQuery() { // elastic query should be always null // not comparison - String actual = evaluateQuery(String.format("task: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String actual = evaluateQuery(String.format("task: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // and comparison @@ -1054,7 +1054,7 @@ public void testComplexElasticTaskQuery() { assert actual == null; // and not comparison - actual = evaluateQuery(String.format("task: id eq '%s' and not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("task: id eq '%s' and title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // or comparison @@ -1062,7 +1062,7 @@ public void testComplexElasticTaskQuery() { assert actual == null; // or not comparison - actual = evaluateQuery(String.format("task: id eq '%s' or not title eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("task: id eq '%s' or title not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // parenthesis comparison @@ -1111,7 +1111,7 @@ public void testSimpleElasticUserQuery() { public void testComplexElasticUserQuery() { // elastic query should be always null // not comparison - String actual = evaluateQuery(String.format("user: not id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + String actual = evaluateQuery(String.format("user: id not eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // and comparison @@ -1119,7 +1119,7 @@ public void testComplexElasticUserQuery() { assert actual == null; // and not comparison - actual = evaluateQuery(String.format("user: id eq '%s' and not email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("user: id eq '%s' and email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // or comparison @@ -1127,7 +1127,7 @@ public void testComplexElasticUserQuery() { assert actual == null; // or not comparison - actual = evaluateQuery(String.format("user: id eq '%s' or not email eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + actual = evaluateQuery(String.format("user: id eq '%s' or email not eq 'test'", GENERIC_OBJECT_ID)).getFullElasticQuery(); assert actual == null; // parenthesis comparison diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index 48de63a153c..fff2840af3e 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -4,14 +4,16 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseMappingService; +import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; -import com.netgrif.application.engine.petrinet.domain.VersionType; import com.netgrif.application.engine.petrinet.domain.dataset.*; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; @@ -27,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; @@ -35,6 +38,14 @@ @ActiveProfiles({"test"}) @ExtendWith(SpringExtension.class) public class SearchCaseTest { + @Autowired + private IWorkflowService workflowService; + + @Autowired + private IElasticCaseService elasticCaseService; + + @Autowired + private IElasticCaseMappingService caseMappingService; public static final String TEST_TRANSITION_ID = "search_test_t1"; @@ -68,6 +79,37 @@ private IUser createUser(String name, String surname, String email, String autho return importHelper.createUser(user, authorities, processRoles); } + private static Case convertToCase(Object caseObject) { + assert caseObject instanceof Case; + return (Case) caseObject; + } + + private static List<Case> convertToCaseList(Object caseListObject) { + assert caseListObject instanceof List<?>; + for (Object caseObject : (List<?>) caseListObject) { + assert caseObject instanceof Case; + } + + return (List<Case>) caseListObject; + } + + private void compareCases(Case actual, Case expected) { + assert actual.getStringId().equals(expected.getStringId()); + } + + private void compareCases(Case actual, List<Case> expected) { + List<String> expectedStringIds = expected.stream().map(Case::getStringId).collect(Collectors.toList()); + + assert expectedStringIds.contains(actual.getStringId()); + } + + private void compareCases(List<Case> actual, List<Case> expected) { + List<String> actualStringIds = actual.stream().map(Case::getStringId).collect(Collectors.toList()); + List<String> expectedStringIds = expected.stream().map(Case::getStringId).collect(Collectors.toList()); + + assert actualStringIds.containsAll(expectedStringIds); + } + @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml"); @@ -80,9 +122,7 @@ public void testSearchById() { assert count == 1; Object foundCase = searchService.search(query); - - assert foundCase instanceof Case; - assert foundCase.equals(caze); + compareCases(convertToCase(foundCase), caze); } @Test @@ -105,16 +145,13 @@ public void testSearchByProcessId() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test @@ -137,16 +174,13 @@ public void testSearchByProcessIdentifier() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); - Object processes = searchService.search(queryMore); - assert processes instanceof List; - assert ((List<Case>) processes).containsAll(List.of(case1, case2)); + Object cases = searchService.search(queryMore); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test @@ -167,16 +201,13 @@ public void testSearchByTitle() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test @@ -187,46 +218,41 @@ public void testSearchByCreationDate() { Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test", net); - String queryEq = String.format("process: creationDate eq '%s'", toDateTimeString(case1.getCreationDate())); - String queryLt = String.format("processes: creationDate lt '%s'", toDateTimeString(case3.getCreationDate())); - String queryLte = String.format("processes: creationDate lte '%s'", toDateTimeString(case3.getCreationDate())); - String queryGt = String.format("processes: creationDate gt '%s'", toDateTimeString(case1.getCreationDate())); - String queryGte = String.format("processes: creationDate gte '%s'", toDateTimeString(case1.getCreationDate())); + String queryEq = String.format("case: creationDate eq %s", toDateTimeString(case1.getCreationDate())); + String queryLt = String.format("cases: processIdentifier eq '%s' and creationDate lt %s", net.getIdentifier(), toDateTimeString(case3.getCreationDate())); + String queryLte = String.format("cases: processIdentifier eq '%s' and creationDate lte %s", net.getIdentifier(), toDateTimeString(case3.getCreationDate())); + String queryGt = String.format("cases: processIdentifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); + String queryGte = String.format("cases: processIdentifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); long count = searchService.count(queryEq); assert count == 1; Object foundCase = searchService.search(queryEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); count = searchService.count(queryLt); assert count == 2; Object cases = searchService.search(queryLt); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); count = searchService.count(queryLte); - assert count == 2; + assert count == 3; cases = searchService.search(queryLte); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2, case3)); + compareCases(convertToCaseList(cases), List.of(case1, case2, case3)); count = searchService.count(queryGt); assert count == 2; cases = searchService.search(queryGt); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case2, case3)); + compareCases(convertToCaseList(cases), List.of(case2, case3)); count = searchService.count(queryGte); - assert count == 2; + assert count == 3; cases = searchService.search(queryGte); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2, case3)); + compareCases(convertToCaseList(cases), List.of(case1, case2, case3)); } @Test @@ -240,9 +266,9 @@ public void testSearchByAuthor() { Case case2 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); Case case3 = importHelper.createCase("Search Test2", net, user2.transformToLoggedUser()); - String query = String.format("case: author eq '%s'", user1.getStringId()); - String queryOther = String.format("case: author eq '%s'", user2.getStringId()); - String queryMore = String.format("cases: author eq '%s'", user1.getStringId()); + String query = String.format("case: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user1.getStringId()); + String queryOther = String.format("case: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user2.getStringId()); + String queryMore = String.format("cases: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user1.getStringId()); long count = searchService.count(query); assert count == 2; @@ -251,16 +277,13 @@ public void testSearchByAuthor() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test @@ -278,9 +301,9 @@ public void testSearchByPlaces() { importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - String query = String.format("case: places.p2.marking eq %s", 1); - String queryOther = String.format("case: places.p1.marking eq %s", 1); - String queryMore = String.format("cases: places.p2.marking eq %s", 1); + String query = String.format("case: processIdentifier eq '%s' AND places.p2.marking eq %s", net.getIdentifier(), 1); + String queryOther = String.format("case: processIdentifier eq '%s' AND places.p1.marking eq %s", net.getIdentifier(), 1); + String queryMore = String.format("cases: processIdentifier eq '%s' AND places.p2.marking eq %s", net.getIdentifier(), 1); long count = searchService.count(query); assert count == 2; @@ -289,20 +312,17 @@ public void testSearchByPlaces() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test - public void testSearchByTaskState() { + public void testSearchByTaskState() { // todo NAE-1997: not indexing tasks.state PetriNet net = importPetriNet("search/search_test.xml"); IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); @@ -327,20 +347,17 @@ public void testSearchByTaskState() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test - public void testSearchByTaskUserId() { + public void testSearchByTaskUserId() { // todo NAE-1997: not indexing tasks.userId PetriNet net = importPetriNet("search/search_test.xml"); IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); @@ -365,20 +382,17 @@ public void testSearchByTaskUserId() { assert count == 1; Object foundCase = searchService.search(query); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryOther); - assert foundCase instanceof Case; - assert foundCase.equals(case3); + compareCases(convertToCase(foundCase), case3); Object cases = searchService.search(queryMore); - assert cases instanceof List; - assert ((List<Case>) cases).containsAll(List.of(case1, case2)); + compareCases(convertToCaseList(cases), List.of(case1, case2)); } @Test - public void testSearchByDataValue() { + public void testSearchByDataValue() throws InterruptedException { PetriNet net = importPetriNet("search/search_test.xml"); IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); @@ -409,8 +423,8 @@ public void testSearchByDataValue() { "boolean_immediate", booleanFalse, "text_immediate", textField, "number_immediate", numberField, - "multichoice_immediate", multichoiceMapField, - "enumeration_immediate", enumerationMapField, + "multichoice_map_immediate", multichoiceMapField, + "enumeration_map_immediate", enumerationMapField, "date_immediate", dateField, "date_time_immediate", dateTimeField ))); @@ -433,11 +447,13 @@ public void testSearchByDataValue() { String queryDateLte = String.format("case: data.date_immediate.value lte %s", SearchUtils.toDateString(LocalDate.now().plusDays(1))); String queryDateGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); String queryDateGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDate.now().minusDays(1))); - String queryDateTimeEq = String.format("case: data.date_immediate.value eq %s", SearchUtils.toDateString((LocalDateTime) case1.getDataSet().get("date_immediate").getRawValue())); - String queryDateTimeLt = String.format("case: data.date_immediate.value lt %s", SearchUtils.toDateString(LocalDateTime.now().plusMinutes(1))); - String queryDateTimeLte = String.format("case: data.date_immediate.value lte %s", SearchUtils.toDateString(LocalDateTime.now().plusMinutes(1))); - String queryDateTimeGt = String.format("case: data.date_immediate.value gt %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); - String queryDateTimeGte = String.format("case: data.date_immediate.value gte %s", SearchUtils.toDateString(LocalDateTime.now().minusMinutes(1))); + String queryDateTimeEq = String.format("case: data.date_time_immediate.value eq %s", SearchUtils.toDateTimeString((LocalDateTime) case1.getDataSet().get("date_time_immediate").getRawValue())); + String queryDateTimeLt = String.format("case: data.date_time_immediate.value lt %s", SearchUtils.toDateTimeString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeLte = String.format("case: data.date_time_immediate.value lte %s", SearchUtils.toDateTimeString(LocalDateTime.now().plusMinutes(1))); + String queryDateTimeGt = String.format("case: data.date_time_immediate.value gt %s", SearchUtils.toDateTimeString(LocalDateTime.now().minusMinutes(1))); + String queryDateTimeGte = String.format("case: data.date_time_immediate.value gte %s", SearchUtils.toDateTimeString(LocalDateTime.now().minusMinutes(1))); + + Thread.sleep(3000); long count = searchService.count(queryTextEq); assert count == 1; @@ -448,17 +464,18 @@ public void testSearchByDataValue() { count = searchService.count(queryBoolean); assert count == 1; - count = searchService.count(queryEnumerationEq); - assert count == 1; - - count = searchService.count(queryEnumerationContains); - assert count == 1; - - count = searchService.count(queryMultichoiceEq); - assert count == 1; - - count = searchService.count(queryMultichoiceContains); - assert count == 1; + // todo NAE-1997: should use keyValue, textValue represents only value +// count = searchService.count(queryEnumerationEq); +// assert count == 1; +// +// count = searchService.count(queryEnumerationContains); +// assert count == 1; +// +// count = searchService.count(queryMultichoiceEq); +// assert count == 1; +// +// count = searchService.count(queryMultichoiceContains); +// assert count == 1; count = searchService.count(queryNumberEq); assert count == 1; @@ -506,92 +523,70 @@ public void testSearchByDataValue() { assert count == 1; Object foundCase = searchService.search(queryTextEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryTextContains); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryBoolean); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryEnumerationEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryEnumerationContains); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryMultichoiceEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryMultichoiceContains); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); + +// foundCase = searchService.search(queryEnumerationEq); +// compareCases(convertToCase(foundCase), case1); +// +// foundCase = searchService.search(queryEnumerationContains); +// compareCases(convertToCase(foundCase), case1); +// +// foundCase = searchService.search(queryMultichoiceEq); +// compareCases(convertToCase(foundCase), case1); +// +// foundCase = searchService.search(queryMultichoiceContains); +// compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryNumberEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryNumberLt); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryNumberLte); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryNumberGt); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryNumberGte); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryDateEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryDateLt); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); - - foundCase = searchService.search(queryDateLte); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); - - foundCase = searchService.search(queryDateGt); - assert foundCase instanceof Case; - assert foundCase.equals(case1); - - foundCase = searchService.search(queryDateGte); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); + +// foundCase = searchService.search(queryDateEq); +// compareCases(convertToCase(foundCase), case1); +// +// foundCase = searchService.search(queryDateLt); +// compareCases(convertToCase(foundCase), List.of(case1, case2)); +// +// foundCase = searchService.search(queryDateLte); +// compareCases(convertToCase(foundCase), List.of(case1, case2)); +// +// foundCase = searchService.search(queryDateGt); +// compareCases(convertToCase(foundCase), case1); +// +// foundCase = searchService.search(queryDateGte); +// compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryDateTimeEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryDateTimeLt); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryDateTimeLte); - assert foundCase instanceof Case; - assert foundCase.equals(case1) || foundCase.equals(case2); + compareCases(convertToCase(foundCase), List.of(case1, case2)); foundCase = searchService.search(queryDateTimeGt); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryDateTimeGte); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); } @Test @@ -610,11 +605,9 @@ public void testSearchByDataOptions() { assert count == 1; Object foundCase = searchService.search(queryEq); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); foundCase = searchService.search(queryContains); - assert foundCase instanceof Case; - assert foundCase.equals(case1); + compareCases(convertToCase(foundCase), case1); } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index 2300a9a70b5..39230de0120 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -74,8 +74,8 @@ private static Task convertToTask(Object taskObject) { private static List<Task> convertToTaskList(Object taskListObject) { assert taskListObject instanceof List<?>; - for (Object userObject : (List<?>) taskListObject) { - assert userObject instanceof Task; + for (Object taskObject : (List<?>) taskListObject) { + assert taskObject instanceof Task; } return (List<Task>) taskListObject; From e40dbf17156d57f0d90f6633aeb1ad83cb512a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 5 Feb 2025 11:47:14 +0100 Subject: [PATCH 16/27] [NAE-1997] Query language - fix case search by processId in mongo --- .../engine/search/QueryLangEvaluator.java | 9 +++++---- .../application/engine/search/antlr4/QueryLang.g4 | 2 +- .../application/engine/search/SearchCaseTest.java | 12 +++--------- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index e7d8e6c7a71..01fb99ed582 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -468,24 +468,25 @@ public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { @Override public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { - StringPath stringPath; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); switch (type) { case CASE: - stringPath = QCase.case$.petriNetId; + QObjectId qObjectId = QCase.case$.petriNetObjectId; + ObjectId objectId = getObjectIdValue(ctx.stringComparison().STRING().getText()); + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId, not)); setElasticQuery(ctx, buildElasticQuery("processId", op, string, not)); break; case TASK: - stringPath = QTask.task.processId; + StringPath stringPath = QTask.task.processId; + setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); } @Override diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 49f52ae81e0..08d72455500 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -126,7 +126,7 @@ dateTimeComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; booleanComparison: (NOT SPACE?)? op=EQ SPACE BOOLEAN ; // special attribute rules -dataValue: DATA '.' fieldId=JAVA_ID '.'VALUE ; +dataValue: DATA '.' fieldId=JAVA_ID '.' VALUE ; dataOptions: DATA '.' fieldId=JAVA_ID '.' OPTIONS ; places: PLACES '.' placeId=JAVA_ID '.' MARKING ; tasksState: TASKS '.' taskId=JAVA_ID '.' STATE ; diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index fff2840af3e..b2f3c457037 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -4,8 +4,6 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.User; -import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseMappingService; -import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.dataset.*; @@ -41,12 +39,6 @@ public class SearchCaseTest { @Autowired private IWorkflowService workflowService; - @Autowired - private IElasticCaseService elasticCaseService; - - @Autowired - private IElasticCaseMappingService caseMappingService; - public static final String TEST_TRANSITION_ID = "search_test_t1"; @Autowired @@ -287,7 +279,7 @@ public void testSearchByAuthor() { } @Test - public void testSearchByPlaces() { + public void testSearchByPlaces() throws InterruptedException { PetriNet net = importPetriNet("search/search_test.xml"); IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); @@ -305,6 +297,8 @@ public void testSearchByPlaces() { String queryOther = String.format("case: processIdentifier eq '%s' AND places.p1.marking eq %s", net.getIdentifier(), 1); String queryMore = String.format("cases: processIdentifier eq '%s' AND places.p2.marking eq %s", net.getIdentifier(), 1); + Thread.sleep(3000); + long count = searchService.count(query); assert count == 2; From 0a0a8cedc23bd3971d7012425a4d227226dccbfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Fri, 14 Feb 2025 04:07:16 +0100 Subject: [PATCH 17/27] [NAE-1997] Query language - add explain query functionality --- .../engine/search/QueryLangErrorListener.java | 29 +- .../engine/search/QueryLangEvaluator.java | 2 +- .../search/QueryLangExplainEvaluator.java | 284 ++++++++++++++++++ .../engine/search/SearchService.java | 2 +- .../search/utils/QueryLangTreeNode.java | 44 +++ .../search/{ => utils}/SearchUtils.java | 44 ++- .../engine/search/QueryLangTest.java | 2 +- .../engine/search/SearchCaseTest.java | 3 +- .../engine/search/SearchProcessTest.java | 2 +- .../engine/search/SearchTaskTest.java | 1 + 10 files changed, 398 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java create mode 100644 src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java rename src/main/java/com/netgrif/application/engine/search/{ => utils}/SearchUtils.java (85%) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java index 55ef0b585a1..03057c23102 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangErrorListener.java @@ -1,15 +1,36 @@ package com.netgrif.application.engine.search; -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.Recognizer; +import lombok.Getter; +import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.ParseCancellationException; +import java.util.ArrayList; +import java.util.List; + +@Getter public class QueryLangErrorListener extends BaseErrorListener { + List<String> errorMessages = new ArrayList<>(); @Override public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { - throw new IllegalArgumentException("line " + line + ":" + charPositionInLine + " " + msg); + errorMessages.add(underlineError(recognizer, (Token) offendingSymbol, line, charPositionInLine, msg)); + } + + protected String underlineError(Recognizer<?, ?> recognizer, Token offendingToken, int line, int charPositionInLine, String msg) { + String underlineErrorMsg = msg + "\n"; + int start = offendingToken.getStartIndex(); + int stop = offendingToken.getStopIndex(); + if (start > stop) { + return underlineErrorMsg; + } + CommonTokenStream tokens = (CommonTokenStream) recognizer.getInputStream(); + String input = tokens.getTokenSource().getInputStream().toString(); + String[] lines = input.split("\n"); + String errorLine = lines[line - 1]; + underlineErrorMsg += errorLine + "\n"; + underlineErrorMsg += " ".repeat(charPositionInLine) + "^".repeat(stop - start + 1) + "\n"; + + return underlineErrorMsg; } } diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 01fb99ed582..9ea05c8ac1c 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -27,7 +27,7 @@ import java.util.Objects; import java.util.stream.Collectors; -import static com.netgrif.application.engine.search.SearchUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.*; public class QueryLangEvaluator extends QueryLangBaseListener { diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java new file mode 100644 index 00000000000..6c8a09da626 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -0,0 +1,284 @@ +package com.netgrif.application.engine.search; + +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; +import com.netgrif.application.engine.search.antlr4.QueryLangParser; +import com.netgrif.application.engine.search.enums.QueryType; +import com.netgrif.application.engine.search.utils.QueryLangTreeNode; +import lombok.Getter; +import org.antlr.v4.runtime.tree.ErrorNodeImpl; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public class QueryLangExplainEvaluator extends QueryLangBaseListener { + + private final ParseTreeProperty<QueryLangTreeNode> node = new ParseTreeProperty<>(); + @Getter + private QueryLangTreeNode root = null; + + @Getter + private QueryType type; + @Getter + private Boolean multiple; + + public void setQueryLangTreeNode(ParseTree node, QueryLangTreeNode queryLangTreeNode) { + this.node.put(node, queryLangTreeNode); + } + + public QueryLangTreeNode getQueryLangTreeNode(ParseTree node) { + return this.node.get(node); + } + + private QueryLangTreeNode getErrorFromNode(ParseTree node) { + if (node instanceof ErrorNodeImpl) { + String errorMsg = "error: " + ((ErrorNodeImpl) node).symbol.getText(); + return new QueryLangTreeNode(errorMsg, new ArrayList<>()); + } + return null; + } + + private List<QueryLangTreeNode> getErrorsFromChildren(List<ParseTree> children) { + List<QueryLangTreeNode> errors = new ArrayList<>(); + children.forEach(child -> { + if (child instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(child)); + } + }); + return errors; + } + + private List<QueryLangTreeNode> getErrorsRecursive(ParseTree node) { + List<QueryLangTreeNode> errors = new ArrayList<>(); + if (node.getChildCount() == 0) { + if (node instanceof ErrorNodeImpl) { + errors.add(getErrorFromNode(node)); + } + return errors; + } + + int numChildren = node.getChildCount(); + for (int i = 0; i < numChildren; i++) { + errors.addAll(getErrorsRecursive(node.getChild(i))); + } + return errors; + } + + private void processComplexExpression(String nodeName, List<ParseTree> children, ParseTree current) { + if (children.size() == 1) { + setQueryLangTreeNode(current, getQueryLangTreeNode(children.get(0))); + return; + } + + List<QueryLangTreeNode> childrenNodes = children.stream() + .map(this::getQueryLangTreeNode) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + setQueryLangTreeNode(current, new QueryLangTreeNode(nodeName, childrenNodes, getErrorsFromChildren(children))); + } + + @Override + public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + type = QueryType.PROCESS; + multiple = ctx.resource.getType() == QueryLangParser.PROCESSES; + } + + @Override + public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { + root = new QueryLangTreeNode("process query", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { + type = QueryType.CASE; + multiple = ctx.resource.getType() == QueryLangParser.CASES; + } + + @Override + public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { + root = new QueryLangTreeNode("case query", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { + type = QueryType.TASK; + multiple = ctx.resource.getType() == QueryLangParser.TASKS; + } + + @Override + public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { + root = new QueryLangTreeNode("task query", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { + type = QueryType.USER; + multiple = ctx.resource.getType() == QueryLangParser.USERS; + } + + @Override + public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { + root = new QueryLangTreeNode("user query", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children)); + } + + @Override + public void exitProcessConditions(QueryLangParser.ProcessConditionsContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processOrExpression())); + } + + @Override + public void exitProcessOrExpression(QueryLangParser.ProcessOrExpressionContext ctx) { + List<ParseTree> children = ctx.processAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitProcessAndExpression(QueryLangParser.ProcessAndExpressionContext ctx) { + List<ParseTree> children = ctx.processConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.processCondition())); + } + + @Override + public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.caseOrExpression())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitCaseOrExpression(QueryLangParser.CaseOrExpressionContext ctx) { + List<ParseTree> children = ctx.caseAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitCaseAndExpression(QueryLangParser.CaseAndExpressionContext ctx) { + List<ParseTree> children = ctx.caseConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseCondition())); + } + + + @Override + public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.taskOrExpression())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitTaskOrExpression(QueryLangParser.TaskOrExpressionContext ctx) { + List<ParseTree> children = ctx.taskAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitTaskAndExpression(QueryLangParser.TaskAndExpressionContext ctx) { + List<ParseTree> children = ctx.taskConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskCondition())); + } + + @Override + public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.userOrExpression())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitUserOrExpression(QueryLangParser.UserOrExpressionContext ctx) { + List<ParseTree> children = ctx.userAndExpression().stream() + .map(andExpression -> (ParseTree) andExpression) + .collect(Collectors.toList()); + + processComplexExpression("OR", children, ctx); + } + + @Override + public void exitUserAndExpression(QueryLangParser.UserAndExpressionContext ctx) { + List<ParseTree> children = ctx.userConditionGroup().stream() + .map(conditionGroup -> (ParseTree) conditionGroup) + .collect(Collectors.toList()); + + processComplexExpression("AND", children, ctx); + } + + @Override + public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicContext ctx) { + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userCondition())); + } + + @Override + public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { + setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children))); + } + + @Override + public void exitProcessCondition(QueryLangParser.ProcessConditionContext ctx) { + List<QueryLangTreeNode> errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitCaseCondition(QueryLangParser.CaseConditionContext ctx) { + List<QueryLangTreeNode> errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitTaskCondition(QueryLangParser.TaskConditionContext ctx) { + List<QueryLangTreeNode> errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } + + @Override + public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { + List<QueryLangTreeNode> errors = getErrorsRecursive(ctx); + setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index 7d7418d18ec..f426bc9cc0c 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -21,7 +21,7 @@ import java.util.List; -import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; +import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; @Slf4j @Service diff --git a/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java new file mode 100644 index 00000000000..fe95b57df73 --- /dev/null +++ b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java @@ -0,0 +1,44 @@ +package com.netgrif.application.engine.search.utils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * https://stackoverflow.com/a/8948691 + */ +public class QueryLangTreeNode { + final String name; + final List<QueryLangTreeNode> children; + + public QueryLangTreeNode(String name, List<QueryLangTreeNode> children) { + this.name = name; + this.children = children; + } + + public QueryLangTreeNode(String name, List<QueryLangTreeNode> children, List<QueryLangTreeNode> errors) { + this.name = name; + this.children = new ArrayList<>(children); + this.children.addAll(errors); + } + + public String toString() { + StringBuilder buffer = new StringBuilder(50); + print(buffer, "", ""); + return buffer.toString(); + } + + private void print(StringBuilder buffer, String prefix, String childrenPrefix) { + buffer.append(prefix); + buffer.append(name); + buffer.append('\n'); + for (Iterator<QueryLangTreeNode> it = children.iterator(); it.hasNext();) { + QueryLangTreeNode next = it.next(); + if (it.hasNext()) { + next.print(buffer, childrenPrefix + "├── ", childrenPrefix + "│ "); + } else { + next.print(buffer, childrenPrefix + "└── ", childrenPrefix + " "); + } + } + } +} diff --git a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java similarity index 85% rename from src/main/java/com/netgrif/application/engine/search/SearchUtils.java rename to src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index 48c49f4c09b..8060bfc7008 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -1,5 +1,9 @@ -package com.netgrif.application.engine.search; +package com.netgrif.application.engine.search.utils; +import com.netgrif.application.engine.search.QueryLangErrorListener; +import com.netgrif.application.engine.search.QueryLangEvaluator; +import com.netgrif.application.engine.search.QueryLangExplainEvaluator; +import com.netgrif.application.engine.search.antlr4.QueryLangBaseListener; import com.netgrif.application.engine.search.antlr4.QueryLangLexer; import com.netgrif.application.engine.search.antlr4.QueryLangParser; import com.netgrif.application.engine.petrinet.domain.QPetriNet; @@ -9,6 +13,7 @@ import com.querydsl.core.types.Predicate; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; +import lombok.extern.slf4j.Slf4j; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.Token; @@ -18,12 +23,12 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.List; import java.util.Map; +@Slf4j public class SearchUtils { public static final Map<ComparisonType, List<Integer>> comparisonOperators = Map.of( @@ -76,7 +81,19 @@ public static LocalDate toDate(String dateString) { } } - public static QueryLangEvaluator evaluateQuery(String input) { + public static QueryLangExplainEvaluator explainQueryInternal(ParseTreeWalker walker, QueryLangParser.QueryContext query, QueryLangErrorListener errorListener) { + QueryLangExplainEvaluator evaluator = new QueryLangExplainEvaluator(); + walker.walk(evaluator, query); + + String treeStringVisualisation = evaluator.getRoot().toString(); + if (!errorListener.getErrorMessages().isEmpty()) { + throw new IllegalArgumentException("\n" + treeStringVisualisation + "\n" + String.join("\n", errorListener.getErrorMessages())); + } + + return evaluator; + } + + private static QueryLangBaseListener evaluateQueryInternal(String input, boolean onlyExplain) { if (input == null || input.isEmpty()) { throw new IllegalArgumentException("Query cannot be empty."); } @@ -84,17 +101,32 @@ public static QueryLangEvaluator evaluateQuery(String input) { QueryLangLexer lexer = new QueryLangLexer(CharStreams.fromString(input)); CommonTokenStream tokens = new CommonTokenStream(lexer); QueryLangParser parser = new QueryLangParser(tokens); + QueryLangErrorListener errorListener = new QueryLangErrorListener(); parser.removeErrorListeners(); - parser.addErrorListener(new QueryLangErrorListener()); + parser.addErrorListener(errorListener); + QueryLangParser.QueryContext query = parser.query(); ParseTreeWalker walker = new ParseTreeWalker(); - QueryLangEvaluator evaluator = new QueryLangEvaluator(); - walker.walk(evaluator, parser.query()); + if (onlyExplain || !errorListener.getErrorMessages().isEmpty()) { + return explainQueryInternal(walker, query, errorListener); + } + + QueryLangEvaluator evaluator = new QueryLangEvaluator(); + walker.walk(evaluator, query); return evaluator; } + public static QueryLangEvaluator evaluateQuery(String input) { + return (QueryLangEvaluator) evaluateQueryInternal(input, false); + } + + public static String explainQuery(String input) { + QueryLangExplainEvaluator evaluator = (QueryLangExplainEvaluator) evaluateQueryInternal(input, true); + return "\n" + evaluator.getRoot().toString(); + } + public static String getStringValue(String queryLangString) { return queryLangString.replace("'", ""); } diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 622b89edbec..67471da66c6 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -23,7 +23,7 @@ import java.sql.Timestamp; import java.time.LocalDateTime; -import static com.netgrif.application.engine.search.SearchUtils.evaluateQuery; +import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; @Slf4j @SpringBootTest diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index b2f3c457037..81924faa9ff 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -9,6 +9,7 @@ import com.netgrif.application.engine.petrinet.domain.dataset.*; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; @@ -29,7 +30,7 @@ import java.util.Set; import java.util.stream.Collectors; -import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; @Slf4j @SpringBootTest diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java index 876f486f36b..91c573ab88e 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.stream.Collectors; -import static com.netgrif.application.engine.search.SearchUtils.toDateTimeString; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; @Slf4j @SpringBootTest diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index 39230de0120..bf9db10f81c 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -7,6 +7,7 @@ import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.Task; From ff592f5e43f790d37de17bcf27b35233ff6823ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Mon, 17 Feb 2025 13:09:54 +0100 Subject: [PATCH 18/27] [NAE-1997] Query language - update explain functionality (add null checks, add more information to output) --- .../logic/action/ActionDelegate.groovy | 4 + .../search/QueryLangExplainEvaluator.java | 110 +++++++++++++++--- .../engine/search/SearchService.java | 6 + .../search/interfaces/ISearchService.java | 1 + .../search/utils/QueryLangTreeNode.java | 5 +- .../engine/search/utils/SearchUtils.java | 5 +- 6 files changed, 110 insertions(+), 21 deletions(-) diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index bf3365ed7c5..82e86f49be1 100644 --- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -2326,6 +2326,10 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { return taskCase.getPetriNet().getDataSet().get(fieldId) } + String explainQuery(String query) { + return searchService.explainQuery(query) + } + long count(String query) { return searchService.count(query) } diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java index 6c8a09da626..fa6b52cc2e8 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +import java.util.stream.Stream; public class QueryLangExplainEvaluator extends QueryLangBaseListener { @@ -23,9 +24,29 @@ public class QueryLangExplainEvaluator extends QueryLangBaseListener { @Getter private QueryType type; @Getter - private Boolean multiple; + private boolean multiple; + @Getter + private boolean searchWithElastic = false; + + public String explain() { + String resource = type != null ? type.name() : "unknown"; + String quantity = multiple ? "multiple instances" : "single instance"; + String db = searchWithElastic ? "Elasticsearch" : "MongoDB"; + String stringTreeVisualisation = root != null ? root.toString() : "Tree visualisation not available."; + return "Searching " + + quantity + + " of resource " + + resource + + " with " + + db + + ".\n" + + stringTreeVisualisation; + } public void setQueryLangTreeNode(ParseTree node, QueryLangTreeNode queryLangTreeNode) { + if (queryLangTreeNode == null) { + queryLangTreeNode = new QueryLangTreeNode("error: " + node.getText()); + } this.node.put(node, queryLangTreeNode); } @@ -33,10 +54,15 @@ public QueryLangTreeNode getQueryLangTreeNode(ParseTree node) { return this.node.get(node); } + private static QueryLangTreeNode createTreeNode(String name, List<QueryLangTreeNode> children, List<QueryLangTreeNode> errors) { + List<QueryLangTreeNode> combinedChildren = Stream.concat(children.stream(), errors.stream()).collect(Collectors.toList()); + return new QueryLangTreeNode(name, combinedChildren); + } + private QueryLangTreeNode getErrorFromNode(ParseTree node) { if (node instanceof ErrorNodeImpl) { String errorMsg = "error: " + ((ErrorNodeImpl) node).symbol.getText(); - return new QueryLangTreeNode(errorMsg, new ArrayList<>()); + return new QueryLangTreeNode(errorMsg); } return null; } @@ -72,13 +98,21 @@ private void processComplexExpression(String nodeName, List<ParseTree> children, setQueryLangTreeNode(current, getQueryLangTreeNode(children.get(0))); return; } + List<QueryLangTreeNode> errorNodes = getErrorsFromChildren(children); List<QueryLangTreeNode> childrenNodes = children.stream() - .map(this::getQueryLangTreeNode) + .map(child -> { + QueryLangTreeNode node = getQueryLangTreeNode(child); + if (node == null) { + errorNodes.addAll(getErrorsRecursive(child)); + } + return node; + }) .filter(Objects::nonNull) .collect(Collectors.toList()); - setQueryLangTreeNode(current, new QueryLangTreeNode(nodeName, childrenNodes, getErrorsFromChildren(children))); + + setQueryLangTreeNode(current, createTreeNode(nodeName, childrenNodes, errorNodes)); } @Override @@ -89,7 +123,7 @@ public void enterProcessQuery(QueryLangParser.ProcessQueryContext ctx) { @Override public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { - root = new QueryLangTreeNode("process query", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children)); + root = createTreeNode("process query", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children)); } @Override @@ -100,7 +134,8 @@ public void enterCaseQuery(QueryLangParser.CaseQueryContext ctx) { @Override public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { - root = new QueryLangTreeNode("case query", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children)); + QueryLangTreeNode childTreeNode = getQueryLangTreeNode(ctx.caseConditions()); + root = createTreeNode("case query", List.of(childTreeNode), getErrorsFromChildren(ctx.children)); } @Override @@ -111,7 +146,7 @@ public void enterTaskQuery(QueryLangParser.TaskQueryContext ctx) { @Override public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { - root = new QueryLangTreeNode("task query", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children)); + root = createTreeNode("task query", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children)); } @Override @@ -122,7 +157,7 @@ public void enterUserQuery(QueryLangParser.UserQueryContext ctx) { @Override public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { - root = new QueryLangTreeNode("user query", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children)); + root = createTreeNode("user query", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children)); } @Override @@ -155,12 +190,12 @@ public void exitProcessConditionGroupBasic(QueryLangParser.ProcessConditionGroup @Override public void exitProcessConditionGroupParenthesis(QueryLangParser.ProcessConditionGroupParenthesisContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.processConditions())), getErrorsFromChildren(ctx.children))); } @Override public void exitCaseConditions(QueryLangParser.CaseConditionsContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.caseOrExpression())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.caseOrExpression())); } @Override @@ -189,12 +224,12 @@ public void exitCaseConditionGroupBasic(QueryLangParser.CaseConditionGroupBasicC @Override public void exitCaseConditionGroupParenthesis(QueryLangParser.CaseConditionGroupParenthesisContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.caseConditions())), getErrorsFromChildren(ctx.children))); } @Override public void exitTaskConditions(QueryLangParser.TaskConditionsContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.taskOrExpression())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.taskOrExpression())); } @Override @@ -222,12 +257,12 @@ public void exitTaskConditionGroupBasic(QueryLangParser.TaskConditionGroupBasicC @Override public void exitTaskConditionGroupParenthesis(QueryLangParser.TaskConditionGroupParenthesisContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.taskConditions())), getErrorsFromChildren(ctx.children))); } @Override public void exitUserConditions(QueryLangParser.UserConditionsContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("", List.of(getQueryLangTreeNode(ctx.userOrExpression())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, getQueryLangTreeNode(ctx.userOrExpression())); } @Override @@ -255,7 +290,7 @@ public void exitUserConditionGroupBasic(QueryLangParser.UserConditionGroupBasicC @Override public void exitUserConditionGroupParenthesis(QueryLangParser.UserConditionGroupParenthesisContext ctx) { - setQueryLangTreeNode(ctx, new QueryLangTreeNode("()", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children))); + setQueryLangTreeNode(ctx, createTreeNode("()", List.of(getQueryLangTreeNode(ctx.userConditions())), getErrorsFromChildren(ctx.children))); } @Override @@ -281,4 +316,49 @@ public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { List<QueryLangTreeNode> errors = getErrorsRecursive(ctx); setQueryLangTreeNode(ctx, new QueryLangTreeNode(ctx.getText(), errors)); } + + @Override + public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataString(QueryLangParser.DataStringContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDate(QueryLangParser.DataDateContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { + this.searchWithElastic = true; + } + + @Override + public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { + this.searchWithElastic = true; + } } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index f426bc9cc0c..45439d04dd7 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -6,6 +6,7 @@ import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.search.interfaces.ISearchService; +import com.netgrif.application.engine.search.utils.SearchUtils; import com.netgrif.application.engine.utils.FullPageRequest; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; @@ -68,6 +69,11 @@ private List<Case> findCasesElastic(String elasticQuery) { ).getContent(); } + @Override + public String explainQuery(String input) { + return SearchUtils.explainQuery(input); + } + @Override public Object search(String input) { QueryLangEvaluator evaluator = evaluateQuery(input); diff --git a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java index 93135262bf1..ef69b1bae89 100644 --- a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java @@ -1,6 +1,7 @@ package com.netgrif.application.engine.search.interfaces; public interface ISearchService { + String explainQuery(String query); Object search(String query); diff --git a/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java index fe95b57df73..440952b211c 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/QueryLangTreeNode.java @@ -16,10 +16,9 @@ public QueryLangTreeNode(String name, List<QueryLangTreeNode> children) { this.children = children; } - public QueryLangTreeNode(String name, List<QueryLangTreeNode> children, List<QueryLangTreeNode> errors) { + public QueryLangTreeNode(String name) { this.name = name; - this.children = new ArrayList<>(children); - this.children.addAll(errors); + this.children = new ArrayList<>(); } public String toString() { diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index 8060bfc7008..c8045307e9a 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -85,9 +85,8 @@ public static QueryLangExplainEvaluator explainQueryInternal(ParseTreeWalker wal QueryLangExplainEvaluator evaluator = new QueryLangExplainEvaluator(); walker.walk(evaluator, query); - String treeStringVisualisation = evaluator.getRoot().toString(); if (!errorListener.getErrorMessages().isEmpty()) { - throw new IllegalArgumentException("\n" + treeStringVisualisation + "\n" + String.join("\n", errorListener.getErrorMessages())); + throw new IllegalArgumentException("\n" + evaluator.explain() + "\n" + String.join("\n", errorListener.getErrorMessages())); } return evaluator; @@ -124,7 +123,7 @@ public static QueryLangEvaluator evaluateQuery(String input) { public static String explainQuery(String input) { QueryLangExplainEvaluator evaluator = (QueryLangExplainEvaluator) evaluateQueryInternal(input, true); - return "\n" + evaluator.getRoot().toString(); + return evaluator.explain(); } public static String getStringValue(String queryLangString) { From 88c9f20c4d580af03f5ed26e1c1f8672fadce0ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 26 Feb 2025 13:10:02 +0100 Subject: [PATCH 19/27] [NAE-1997] Query language - implement paging and sorting functionality --- .../logic/action/ActionDelegate.groovy | 4 + .../elastic/service/ElasticCaseService.java | 16 + .../engine/search/QueryLangEvaluator.java | 92 +++- .../search/QueryLangExplainEvaluator.java | 111 ++++- .../engine/search/SearchService.java | 62 ++- .../engine/search/antlr4/QueryLang.g4 | 71 ++- .../search/interfaces/ISearchService.java | 2 + .../engine/search/utils/SearchUtils.java | 45 ++ .../engine/search/QueryLangTest.java | 448 +++++++++++++++++- 9 files changed, 799 insertions(+), 52 deletions(-) diff --git a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy index 82e86f49be1..065b70c23c4 100644 --- a/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy +++ b/src/main/groovy/com/netgrif/application/engine/petrinet/domain/dataset/logic/action/ActionDelegate.groovy @@ -2337,4 +2337,8 @@ class ActionDelegate /*TODO: release/8.0.0: implements ActionAPI*/ { def search(String query) { return searchService.search(query) } + + boolean exists(String query) { + return searchService.exists(query) + } } \ No newline at end of file diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java index bd7fc69ac29..f657496a6a9 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java @@ -156,6 +156,22 @@ public long count(List<CaseSearchRequest> requests, LoggedUser user, Locale loca } } + // todo NAE-1997: implement exists -> now it only supports exists by id, we need exists + query +// @Override +// public boolean exists(List<CaseSearchRequest> requests, LoggedUser user, Locale locale, Boolean isIntersection) { +// if (requests == null) { +// throw new IllegalArgumentException("Request can not be null!"); +// } +// +// LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); +// NativeSearchQuery query = buildQuery(requests, loggedOrImpersonated, new FullPageRequest(), locale, isIntersection); +// if (query != null) { +// return template.exists(query, ElasticCase.class); +// } else { +// return false; +// } +// } + public String findUriNodeId(Case aCase) { if (aCase == null) { return null; diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 9ea05c8ac1c..7f80a6d55e6 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -20,11 +20,13 @@ import org.antlr.v4.runtime.tree.ParseTreeProperty; import org.bson.types.ObjectId; import org.bson.types.QObjectId; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import java.sql.Timestamp; import java.time.LocalDateTime; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.stream.Collectors; import static com.netgrif.application.engine.search.utils.SearchUtils.*; @@ -45,6 +47,12 @@ public class QueryLangEvaluator extends QueryLangBaseListener { private Predicate fullMongoQuery; @Getter private String fullElasticQuery; + @Getter + private Pageable pageable; + + private int pageNumber = 0; + private int pageSize = 20; + private final List<Sort.Order> sortOrders = new ArrayList<>(); public void setElasticQuery(ParseTree node, String query) { elasticQuery.put(node, query); @@ -136,6 +144,7 @@ public void exitProcessQuery(QueryLangParser.ProcessQueryContext ctx) { processBasicExpression(ctx.processConditions(), ctx); fullMongoQuery = getMongoQuery(ctx); fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); } @Override @@ -149,6 +158,7 @@ public void exitCaseQuery(QueryLangParser.CaseQueryContext ctx) { processBasicExpression(ctx.caseConditions(), ctx); fullMongoQuery = getMongoQuery(ctx); fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); } @Override @@ -162,6 +172,7 @@ public void exitTaskQuery(QueryLangParser.TaskQueryContext ctx) { processBasicExpression(ctx.taskConditions(), ctx); fullMongoQuery = getMongoQuery(ctx); fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); } @Override @@ -175,6 +186,7 @@ public void exitUserQuery(QueryLangParser.UserQueryContext ctx) { processBasicExpression(ctx.userConditions(), ctx); fullMongoQuery = getMongoQuery(ctx); fullElasticQuery = getElasticQuery(ctx); + pageable = PageRequest.of(pageNumber, pageSize, Sort.by(sortOrders)); } @Override @@ -642,7 +654,7 @@ public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); boolean not = ctx.numberComparison().NOT() != null; - String number = ctx.numberComparison().NUMBER().getText(); + String number = ctx.numberComparison().number.getText(); setMongoQuery(ctx, null); setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op, number, not)); @@ -707,7 +719,7 @@ public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); boolean not = ctx.numberComparison().NOT() != null; - String numberValue = ctx.numberComparison().NUMBER().getText(); + String numberValue = ctx.numberComparison().number.getText(); setMongoQuery(ctx, null); setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue, not)); @@ -739,4 +751,76 @@ public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonConte setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op, string, not)); this.searchWithElastic = true; } + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo NAE-1997: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + return; + } + sortOrders.add(new Sort.Order(dir, prop)); + }); + } } diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java index fa6b52cc2e8..219c973bd3f 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -8,6 +8,7 @@ import org.antlr.v4.runtime.tree.ErrorNodeImpl; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.springframework.data.domain.Sort; import java.util.ArrayList; import java.util.List; @@ -15,6 +16,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static com.netgrif.application.engine.search.utils.SearchUtils.*; + public class QueryLangExplainEvaluator extends QueryLangBaseListener { private final ParseTreeProperty<QueryLangTreeNode> node = new ParseTreeProperty<>(); @@ -28,19 +31,30 @@ public class QueryLangExplainEvaluator extends QueryLangBaseListener { @Getter private boolean searchWithElastic = false; + private int pageNumber = 0; + private int pageSize = 20; + private final List<String> sortOrders = new ArrayList<>(); + public String explain() { - String resource = type != null ? type.name() : "unknown"; - String quantity = multiple ? "multiple instances" : "single instance"; - String db = searchWithElastic ? "Elasticsearch" : "MongoDB"; - String stringTreeVisualisation = root != null ? root.toString() : "Tree visualisation not available."; - return "Searching " + - quantity + - " of resource " + - resource + - " with " + - db + - ".\n" + - stringTreeVisualisation; + StringBuilder result = new StringBuilder("Searching "); + + result.append(multiple ? "multiple instances" : "single instance") + .append(" of resource ") + .append(type != null ? type.name() : "invalid type") + .append(" with ") + .append(searchWithElastic ? "Elasticsearch" : "MongoDB") + .append(".\n") + .append("page number: ").append(pageNumber) + .append(", page size: ").append(pageSize) + .append("\n"); + + if (!sortOrders.isEmpty()) { + result.append("sort by ").append(String.join(";", sortOrders)).append("\n"); + } + + result.append(root != null ? root.toString() : "Tree visualisation not available."); + + return result.toString(); } public void setQueryLangTreeNode(ParseTree node, QueryLangTreeNode queryLangTreeNode) { @@ -361,4 +375,77 @@ public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { this.searchWithElastic = true; } + + + @Override + public void exitPaging(QueryLangParser.PagingContext ctx) { + pageNumber = Integer.parseInt(ctx.pageNum.getText()); + + if (ctx.pageSize != null) { + pageSize = Integer.parseInt(ctx.pageSize.getText()); + } + } + + @Override + public void exitCaseSorting(QueryLangParser.CaseSortingContext ctx) { + ctx.caseAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop; + if (searchWithElastic) { + // todo NAE-1997: sorting by data value, options + if (attrOrd.caseAttribute().places() != null) { + prop = "places." + attrOrd.caseAttribute().places().placeId.getText() + ".marking"; + } else if (attrOrd.caseAttribute().tasksState() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksState().taskId.getText() + ".state.keyword"; + } else if (attrOrd.caseAttribute().tasksUserId() != null) { + prop = "tasks." + attrOrd.caseAttribute().tasksUserId().taskId.getText() + ".userId.keyword"; + } else { + prop = caseAttrToSortPropElasticMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + } else { + prop = caseAttrToSortPropMapping.get(attrOrd.caseAttribute().getText().toLowerCase()); + } + + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.caseAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitProcessSorting(QueryLangParser.ProcessSortingContext ctx) { + ctx.processAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = processAttrToSortPropMapping.get(attrOrd.processAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.processAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitTaskSorting(QueryLangParser.TaskSortingContext ctx) { + ctx.taskAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = taskAttrToSortPropMapping.get(attrOrd.taskAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.taskAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } + + @Override + public void exitUserSorting(QueryLangParser.UserSortingContext ctx) { + ctx.userAttributeOrdering().forEach(attrOrd -> { + Sort.Direction dir = attrOrd.ordering != null ? Sort.Direction.fromString(attrOrd.ordering.getText()) : Sort.Direction.ASC; + String prop = userAttrToSortPropMapping.get(attrOrd.userAttribute().getText().toLowerCase()); + if (prop == null) { + sortOrders.add("Invalid attribute: " + attrOrd.userAttribute().getText()); + } + sortOrders.add("attribute: " + prop + ", ordering: " + dir); + }); + } } diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index 45439d04dd7..2910b9874e2 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -4,20 +4,20 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; +import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.search.utils.SearchUtils; -import com.netgrif.application.engine.utils.FullPageRequest; import com.netgrif.application.engine.workflow.domain.Case; import com.netgrif.application.engine.workflow.domain.repositories.CaseRepository; import com.netgrif.application.engine.workflow.domain.repositories.TaskRepository; -import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.querydsl.core.types.Predicate; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.List; @@ -29,20 +29,14 @@ @RequiredArgsConstructor public class SearchService implements ISearchService { - public static final int DEFAULT_ELASTIC_PAGE_SIZE = 100000; - private final PetriNetRepository petriNetRepository; private final IElasticCaseService elasticCaseService; private final CaseRepository caseRepository; - private final IWorkflowService workflowService; - private final TaskRepository taskRepository; - private final ITaskService taskService; - private final UserRepository userRepository; private final IUserService userService; @@ -58,17 +52,23 @@ private Long countCasesElastic(String elasticQuery) { ); } - private List<Case> findCasesElastic(String elasticQuery) { + private List<Case> findCasesElastic(String elasticQuery, Pageable pageable) { CaseSearchRequest caseSearchRequest = new CaseSearchRequest(); caseSearchRequest.query = elasticQuery; return elasticCaseService.search( List.of(caseSearchRequest), userService.getLoggedOrSystem().transformToLoggedUser(), - PageRequest.of(0, DEFAULT_ELASTIC_PAGE_SIZE), LocaleContextHolder.getLocale(), + pageable, + LocaleContextHolder.getLocale(), false ).getContent(); } + private boolean existsCasesElastic(String elasticQuery) { + // todo NAE-1997: implement exists to elasticCaseService + return countCasesElastic(elasticQuery) > 0; + } + @Override public String explainQuery(String input) { return SearchUtils.explainQuery(input); @@ -79,32 +79,35 @@ public Object search(String input) { QueryLangEvaluator evaluator = evaluateQuery(input); Predicate predicate = evaluator.getFullMongoQuery(); String elasticQuery = evaluator.getFullElasticQuery(); + Pageable pageable = evaluator.getPageable(); switch (evaluator.getType()) { case PROCESS: if (evaluator.getMultiple()) { - return petriNetRepository.findAll(predicate, new FullPageRequest()).getContent(); + return petriNetRepository.findAll(predicate, pageable).getContent(); } return petriNetRepository.findAll(predicate, PageRequest.of(0, 1)) .getContent().stream().findFirst().orElse(null); case CASE: if (!evaluator.getSearchWithElastic()) { if (evaluator.getMultiple()) { - return workflowService.searchAll(predicate).getContent(); + return caseRepository.findAll(predicate, pageable).getContent(); } - return workflowService.searchAll(predicate).getContent().stream().findFirst().orElse(null); + return caseRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); } - List<Case> cases = findCasesElastic(elasticQuery); - return evaluator.getMultiple() ? cases : cases.get(0); + List<Case> cases = findCasesElastic(elasticQuery, pageable); + return evaluator.getMultiple() ? cases : cases.stream().findFirst().orElse(null); case TASK: if (evaluator.getMultiple()) { - return taskService.searchAll(predicate).getContent(); + return taskRepository.findAll(predicate, pageable).getContent(); } - return taskService.searchAll(predicate).getContent().stream().findFirst().orElse(null); + return taskRepository.findAll(predicate, PageRequest.of(0, 1)) + .getContent().stream().findFirst().orElse(null); case USER: if (evaluator.getMultiple()) { - return userRepository.findAll(predicate, new FullPageRequest()).getContent(); + return userRepository.findAll(predicate, pageable).getContent(); } return userRepository.findAll(predicate, PageRequest.of(0, 1)) .getContent().stream().findFirst().orElse(null); @@ -134,4 +137,25 @@ public Long count(String input) { return null; } + @Override + public boolean exists(String input) { + QueryLangEvaluator evaluator = evaluateQuery(input); + Predicate predicate = evaluator.getFullMongoQuery(); + String elasticQuery = evaluator.getFullElasticQuery(); + + switch (evaluator.getType()) { + case PROCESS: + return petriNetRepository.exists(predicate); + case CASE: + if (!evaluator.getSearchWithElastic()) { + return caseRepository.exists(predicate); + } + return existsCasesElastic(elasticQuery); + case TASK: + return taskRepository.exists(predicate); + case USER: + return userRepository.exists(predicate); + } + return false; + } } diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 08d72455500..7a12c40c61d 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -1,10 +1,10 @@ // todo NAE-1997: generate this with plugin grammar QueryLang; -query: resource=(PROCESS | PROCESSES) delimeter processConditions EOF # processQuery - | resource=(CASE | CASES) delimeter caseConditions EOF # caseQuery - | resource=(TASK | TASKS) delimeter taskConditions EOF # taskQuery - | resource=(USER | USERS) delimeter userConditions EOF # userQuery +query: resource=(PROCESS | PROCESSES) delimeter processConditions (paging)? (processSorting)? EOF # processQuery + | resource=(CASE | CASES) delimeter caseConditions (paging)? (caseSorting)? EOF # caseQuery + | resource=(TASK | TASKS) delimeter taskConditions (paging)? (taskSorting)? EOF # taskQuery + | resource=(USER | USERS) delimeter userConditions (paging)? (userSorting)? EOF # userQuery ; processConditions: processOrExpression ; @@ -44,6 +44,55 @@ delimeter: WHERE_DELIMETER | COLON_DELIMETER ; WHERE_DELIMETER: SPACE W H E R E SPACE ; COLON_DELIMETER: SPACE? ':' SPACE ; +// paging +paging: PAGE SPACE pageNum=INT (SPACE SIZE SPACE pageSize=INT)? SPACE?; + +// sorting +processSorting: SORT_BY SPACE processAttributeOrdering (',' SPACE? processAttributeOrdering)* SPACE?; +processAttributeOrdering: processAttribute (SPACE ordering=(ASC | DESC))? ; +processAttribute: ID + | IDENTIFIER + | VERSION + | TITLE + | CREATION_DATE + ; + +caseSorting: SORT_BY SPACE caseAttributeOrdering (',' SPACE? caseAttributeOrdering)* SPACE?; +caseAttributeOrdering: caseAttribute (SPACE ordering=(ASC | DESC))? ; +caseAttribute: ID + | PROCESS_ID + | PROCESS_IDENTIFIER + | TITLE + | CREATION_DATE + | AUTHOR + | places + | tasksUserId + | tasksState + | dataValue + | dataOptions + ; + +taskSorting: SORT_BY SPACE taskAttributeOrdering (',' SPACE? taskAttributeOrdering)* SPACE?; +taskAttributeOrdering: taskAttribute (SPACE ordering=(ASC | DESC))? ; +taskAttribute: ID + | TRANSITION_ID + | TITLE + | STATE + | USER_ID + | CASE_ID + | PROCESS_ID + | LAST_ASSIGN + | LAST_FINISH + ; + +userSorting: SORT_BY SPACE userAttributeOrdering (',' SPACE? userAttributeOrdering)* SPACE?; +userAttributeOrdering: userAttribute (SPACE ordering=(ASC | DESC))? ; +userAttribute: ID + | NAME + | SURNAME + | EMAIL + ; + // resource comparisons processComparisons: idComparison | identifierComparison @@ -120,7 +169,7 @@ tasksUserIdComparison: tasksUserId SPACE stringComparison ; // basic comparisons objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; stringComparison: (NOT SPACE?)? op=(EQ | CONTAINS) SPACE STRING ; -numberComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE NUMBER ; +numberComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE number=(INT | DOUBLE) ; dateComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; dateTimeComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; booleanComparison: (NOT SPACE?)? op=EQ SPACE BOOLEAN ; @@ -180,10 +229,20 @@ MARKING: M A R K I N G ; ENABLED: E N A B L E D ; DISABLED: D I S A B L E D ; +// paging +PAGE: P A G E ; +SIZE: S I Z E ; + +// sorting +SORT_BY: S O R T SPACE B Y ; +ASC: A S C ; +DESC: D E S C ; + // basic types LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? -NUMBER: DIGIT+ ('.' DIGIT+)? ; +INT: DIGIT+ ; +DOUBLE: DIGIT+ '.' DIGIT+ ; DATETIME: DATE 'T' ([01] DIGIT | '2' [0-3]) ':' [0-5] DIGIT ':' [0-5] DIGIT ('.' DIGIT+)? ; // 2020-03-03T20:00:00.055 // todo NAE-1997: format DATE: DIGIT DIGIT DIGIT DIGIT '-' ('0' [1-9] | '1' [0-2]) '-' ('0' [1-9] | [12] DIGIT | '3' [01]) ; // 2020-03-03 // todo NAE-1997: format BOOLEAN: T R U E | F A L S E ; diff --git a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java index ef69b1bae89..b8dddee63bc 100644 --- a/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/interfaces/ISearchService.java @@ -6,4 +6,6 @@ public interface ISearchService { Object search(String query); Long count(String query); + + boolean exists(String query); } diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index c8045307e9a..40a13ca51f2 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -41,6 +41,51 @@ public class SearchUtils { ComparisonType.OPTIONS, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS) ); + public static final Map<String, String> processAttrToSortPropMapping = Map.of( + "id", "id", + "identifier", "identifier", + "version", "version", + "title", "title.defaultValue", + "creationdate", "creationDate" + ); + + public static final Map<String, String> caseAttrToSortPropMapping = Map.of( + "id", "id", + "processidentifier", "processIdentifier", + "processid", "petriNetObjectId", + "title", "title", + "creationdate", "creationDate", + "author", "author.id" + ); + + public static final Map<String, String> caseAttrToSortPropElasticMapping = Map.of( + "id", "stringId.keyword", + "processidentifier", "processIdentifier.keyword", + "processid", "processId.keyword", + "title", "title.keyword", + "creationdate", "creationDateSortable", + "author", "author.keyword" + ); + + public static final Map<String, String> taskAttrToSortPropMapping = Map.of( + "id", "id", + "transitionid", "transitionId", + "title", "title.defaultValue", + "state", "state", + "userid", "userId", + "caseid", "caseId", + "processid", "processId", + "lastassign", "lastAssigned", + "lastfinish", "lastFinished" + ); + + public static final Map<String, String> userAttrToSortPropMapping = Map.of( + "id", "id", + "name", "name", + "surname", "surname", + "email", "email" + ); + public static String toDateString(LocalDate localDate) { return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); } diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 67471da66c6..2ddfa632332 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -16,14 +16,18 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoOperations; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.List; import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; +import static com.netgrif.application.engine.search.utils.SearchUtils.explainQuery; @Slf4j @SpringBootTest @@ -198,14 +202,11 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // processId comparison - actual = evaluateQuery("case: processId eq 'test'").getFullMongoQuery(); - expected = QCase.case$.petriNetId.eq("test"); + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.eq(GENERIC_OBJECT_ID); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("case: processId contains 'test'").getFullMongoQuery(); - expected = QCase.case$.petriNetId.contains("test"); - compareMongoQueries(mongoDbUtils, actual, expected); // processIdentifier comparison @@ -776,12 +777,8 @@ public void testSimpleElasticCaseQuery() { assert expected.equals(actual); // processId comparison - actual = evaluateQuery("case: processId eq 'test'").getFullElasticQuery(); - expected = "processId:test"; - assert expected.equals(actual); - - actual = evaluateQuery("case: processId contains 'test'").getFullElasticQuery(); - expected = "processId:*test*"; + actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); // processIdentifier comparison @@ -1143,6 +1140,435 @@ public void testComplexElasticUserQuery() { assert actual == null; } + @Test + public void testPagingQuery() { + // default + Pageable pageable = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assert pageable.getPageNumber() == 0; + assert pageable.getPageSize() == 20; + + // page number only + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2").getPageable(); + assert pageable.getPageNumber() == 2; + assert pageable.getPageSize() == 20; + + // page number and page size + pageable = evaluateQuery("cases: processIdentifier eq 'test' page 2 size 4").getPageable(); + assert pageable.getPageNumber() == 2; + assert pageable.getPageSize() == 4; + } + + @Test + public void testProcessSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("processes: identifier eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("processes: identifier eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List<Sort.Order> orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by identifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("identifier"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.defaultValue"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by version asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("version"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("processes: identifier eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDate"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + // complex set ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("processes: identifier eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testCaseSortingMongoDbQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: processIdentifier eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List<Sort.Order> orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processIdentifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processIdentifier"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("petriNetObjectId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDate"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by author desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("author.id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("cases: processIdentifier eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testCaseSortingElasticQuery() { + // default (no sort) + Pageable actual = evaluateQuery("cases: data.field1.value eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List<Sort.Order> orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processIdentifier desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processIdentifier.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by creationDate asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("creationDateSortable"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by author desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("author.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by places.p1.marking desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("places.p1.marking"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.state desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("tasks.t1.state.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by tasks.t1.userId desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("tasks.t1.userId.keyword"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.keyword"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("cases: data.field1.value eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("stringId.keyword"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.keyword"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testTaskSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("tasks: title eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("tasks: title eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List<Sort.Order> orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("tasks: title eq 'test' sort by transitionId desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("transitionId"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("tasks: title eq 'test' sort by title asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("title.defaultValue"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by processId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("processId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by caseId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("caseId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by userId asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("userId"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("tasks: title eq 'test' sort by lastAssign asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("lastAssigned"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + actual = evaluateQuery("tasks: title eq 'test' sort by lastFinish desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("lastFinished"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + // complex set ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("tasks: title eq 'test' sort by id asc, title").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("title.defaultValue"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + + @Test + public void testUserSortingQuery() { + // default (no sort) + Pageable actual = evaluateQuery("users: name eq 'test'").getPageable(); + assert !actual.getSort().isSorted(); + + // default ordering asc + actual = evaluateQuery("users: name eq 'test' sort by id").getPageable(); + assert actual.getSort().isSorted(); + List<Sort.Order> orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + + // set ordering + actual = evaluateQuery("users: name eq 'test' sort by id desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("users: name eq 'test' sort by name desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("name"); + assert orders.get(0).getDirection() == Sort.Direction.DESC; + + actual = evaluateQuery("users: name eq 'test' sort by surname asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("surname"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + actual = evaluateQuery("users: name eq 'test' sort by email asc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 1; + assert orders.get(0).getProperty().equals("email"); + assert orders.get(0).getDirection() == Sort.Direction.ASC; + + // complex set ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name desc").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("name"); + assert orders.get(1).getDirection().equals(Sort.Direction.DESC); + + // complex default ordering + actual = evaluateQuery("users: name eq 'test' sort by id asc, name").getPageable(); + assert actual.getSort().isSorted(); + orders = actual.getSort().toList(); + assert orders.size() == 2; + assert orders.get(0).getProperty().equals("id"); + assert orders.get(0).getDirection().equals(Sort.Direction.ASC); + assert orders.get(1).getProperty().equals("name"); + assert orders.get(1).getDirection().equals(Sort.Direction.ASC); + } + @Test public void testProcessQueriesFail() { // using case, task, user attributes From 87a47c4d3199da6fad0306f13a9f5c6e0e38b5ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 5 Mar 2025 02:56:36 +0100 Subject: [PATCH 20/27] [NAE-1997] Query language - add in list/in range operators --- .../engine/search/QueryLangEvaluator.java | 607 ++++++++++++++++-- .../search/QueryLangExplainEvaluator.java | 53 +- .../engine/search/antlr4/QueryLang.g4 | 124 +++- .../engine/search/utils/SearchUtils.java | 135 +++- 4 files changed, 802 insertions(+), 117 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 7f80a6d55e6..942e54c581a 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -18,6 +18,7 @@ import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ParseTree; import org.antlr.v4.runtime.tree.ParseTreeProperty; +import org.antlr.v4.runtime.tree.TerminalNode; import org.bson.types.ObjectId; import org.bson.types.QObjectId; import org.springframework.data.domain.PageRequest; @@ -375,7 +376,7 @@ public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { break; case CASE: qObjectId = QCase.case$.id; - setElasticQuery(ctx, buildElasticQuery("stringId", op, objectId.toString(), not)); + setElasticQuery(ctx, buildElasticQuery("stringId", op.getType(), objectId.toString(), not)); break; case TASK: qObjectId = QTask.task.id; @@ -387,11 +388,11 @@ public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId, not)); + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); } @Override - public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { + public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { StringPath stringPath; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; @@ -403,7 +404,7 @@ public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { break; case CASE: stringPath = QCase.case$.title; - setElasticQuery(ctx, buildElasticQuery("title", op, string, not)); + setElasticQuery(ctx, buildElasticQuery("title", op.getType(), string, not)); break; case TASK: stringPath = QTask.task.title.defaultValue; @@ -412,30 +413,121 @@ public void exitTitleComparison(QueryLangParser.TitleComparisonContext ctx) { throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); } @Override - public void exitIdentifierComparison(QueryLangParser.IdentifierComparisonContext ctx) { + public void exitTitleList(QueryLangParser.TitleListContext ctx) { + StringPath stringPath; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInList("title", stringList, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { + StringPath stringPath; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + switch (type) { + case PROCESS: + stringPath = QPetriNet.petriNet.title.defaultValue; + break; + case CASE: + stringPath = QCase.case$.title; + setElasticQuery(ctx, buildElasticQueryInRange("title", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + break; + case TASK: + stringPath = QTask.task.title.defaultValue; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitIdentifierBasic(QueryLangParser.IdentifierBasicContext ctx) { StringPath stringPath = QPetriNet.petriNet.identifier; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitIdentifierList(QueryLangParser.IdentifierListContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitIdentifierRange(QueryLangParser.IdentifierRangeContext ctx) { + StringPath stringPath = QPetriNet.petriNet.identifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); } @Override - public void exitVersionComparison(QueryLangParser.VersionComparisonContext ctx) { + public void exitVersionBasic(QueryLangParser.VersionBasicContext ctx) { Token op = ctx.op; boolean not = ctx.NOT() != null; String versionString = ctx.VERSION_NUMBER().getText(); - setMongoQuery(ctx, buildVersionPredicate(op, versionString, not)); + setMongoQuery(ctx, buildVersionPredicate(op.getType(), versionString, not)); + } + + @Override + public void exitVersionListCmp(QueryLangParser.VersionListCmpContext ctx) { + boolean not = ctx.inListVersionComparison().NOT() != null; + List<String> stringList = ctx.inListVersionComparison().versionList().VERSION_NUMBER().stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildVersionPredicateInList(stringList, not)); } @Override - public void exitCdDate(QueryLangParser.CdDateContext ctx) { + public void exitVersionRangeCmp(QueryLangParser.VersionRangeCmpContext ctx) { + boolean not = ctx.inRangeVersionComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(0).getText()); + String rightString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(1).getText()); + + setMongoQuery(ctx, buildVersionPredicateInRange(leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitCdDateBasic(QueryLangParser.CdDateBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath; Token op = ctx.dateComparison().op; boolean not = ctx.dateComparison().NOT() != null; @@ -447,17 +539,17 @@ public void exitCdDate(QueryLangParser.CdDateContext ctx) { break; case CASE: dateTimePath = QCase.case$.creationDate; - setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); } @Override - public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { + public void exitCdDateTimeBasic(QueryLangParser.CdDateTimeBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath; Token op = ctx.dateTimeComparison().op; boolean not = ctx.dateTimeComparison().NOT() != null; @@ -469,47 +561,128 @@ public void exitCdDateTime(QueryLangParser.CdDateTimeContext ctx) { break; case CASE: dateTimePath = QCase.case$.creationDate; - setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + setElasticQuery(ctx, buildElasticQuery("creationDateSortable", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); } @Override - public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { - Token op = ctx.stringComparison().op; - boolean not = ctx.stringComparison().NOT() != null; - String string = getStringValue(ctx.stringComparison().STRING().getText()); + public void exitCdDateList(QueryLangParser.CdDateListContext ctx) { + DateTimePath<LocalDateTime> dateTimePath; + boolean not = ctx.inListDateComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List<String> stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; case CASE: - QObjectId qObjectId = QCase.case$.petriNetObjectId; - ObjectId objectId = getObjectIdValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op, objectId, not)); - setElasticQuery(ctx, buildElasticQuery("processId", op, string, not)); + dateTimePath = QCase.case$.creationDate; + List<String> timestampStringList = stringDateList.stream().map(dateString -> { + LocalDateTime localDateTime = toDateTime(dateString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + setElasticQuery(ctx, buildElasticQueryInList("creationDateSortable", timestampStringList, not)); break; - case TASK: - StringPath stringPath = QTask.task.processId; - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { + DateTimePath<LocalDateTime> dateTimePath; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + + switch (type) { + case PROCESS: + dateTimePath = QPetriNet.petriNet.creationDate; + break; + case CASE: + dateTimePath = QCase.case$.creationDate; + setElasticQuery(ctx, buildElasticQueryInRange("creationDateSortable", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); break; default: throw new IllegalArgumentException("Unknown query type: " + type); } + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); } @Override - public void exitProcessIdentifierComparison(QueryLangParser.ProcessIdentifierComparisonContext ctx) { + public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { + StringPath stringPath = QTask.task.processId; + Token op = ctx.stringComparison().op; + boolean not = ctx.stringComparison().NOT() != null; + String string = getStringValue(ctx.stringComparison().STRING().getText()); + + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitProcessIdObjIdComparison(QueryLangParser.ProcessIdObjIdComparisonContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + Token op = ctx.objectIdComparison().op; + boolean not = ctx.objectIdComparison().NOT() != null; + ObjectId objectId = getObjectIdValue(ctx.objectIdComparison().STRING().getText()); + + setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); + setElasticQuery(ctx, buildElasticQuery("processId", op.getType(), objectId.toString(), not)); + } + + @Override + public void exitProcessIdentifierBasic(QueryLangParser.ProcessIdentifierBasicContext ctx) { StringPath stringPath = QCase.case$.processIdentifier; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); - setElasticQuery(ctx, buildElasticQuery("processIdentifier", op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("processIdentifier", op.getType(), string, not)); + } + + @Override + public void exitProcessIdentifierList(QueryLangParser.ProcessIdentifierListContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeContext ctx) { + StringPath stringPath = QCase.case$.processIdentifier; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); } @Override @@ -519,18 +692,39 @@ public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); - setElasticQuery(ctx, buildElasticQuery("author", op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + setElasticQuery(ctx, buildElasticQuery("author", op.getType(), string, not)); } @Override - public void exitTransitionIdComparison(QueryLangParser.TransitionIdComparisonContext ctx) { + public void exitTransitionIdBasic(QueryLangParser.TransitionIdBasicContext ctx) { StringPath stringPath = QTask.task.transitionId; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitTransitionIdList(QueryLangParser.TransitionIdListContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) { + StringPath stringPath = QTask.task.transitionId; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); } @Override @@ -552,7 +746,7 @@ public void exitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); } @Override @@ -562,77 +756,206 @@ public void exitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); } @Override - public void exitLaDate(QueryLangParser.LaDateContext ctx) { + public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; Token op = ctx.dateComparison().op; boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); } @Override - public void exitLaDateTime(QueryLangParser.LaDateTimeContext ctx) { + public void exitLaDateTimeBasic(QueryLangParser.LaDateTimeBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; Token op = ctx.dateTimeComparison().op; boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); } @Override - public void exitLfDate(QueryLangParser.LfDateContext ctx) { + public void exitLaDateList(QueryLangParser.LaDateListContext ctx) { + DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; + boolean not = ctx.inListDateComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List<String> stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); + } + + @Override + public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { + DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitLfDateBasic(QueryLangParser.LfDateBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; Token op = ctx.dateComparison().op; boolean not = ctx.dateComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); } @Override - public void exitLfDateTime(QueryLangParser.LfDateTimeContext ctx) { + public void exitLfDateTimeBasic(QueryLangParser.LfDateTimeBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; Token op = ctx.dateTimeComparison().op; boolean not = ctx.dateTimeComparison().NOT() != null; LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); - setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op, localDateTime, not)); + setMongoQuery(ctx, buildDateTimePredicate(dateTimePath, op.getType(), localDateTime, not)); + } + + @Override + public void exitLfDateList(QueryLangParser.LfDateListContext ctx) { + DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; + boolean not = ctx.inListDateComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME() ; + List<String> stringDateList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, buildDateTimePredicateInList(dateTimePath, stringDateList, not)); } @Override - public void exitNameComparison(QueryLangParser.NameComparisonContext ctx) { + public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) { + DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastFinished; + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, buildDateTimePredicateInRange(dateTimePath, leftDateTime, leftEndpointOpen, rightDateTime, rightEndpointOpen, not)); + } + + @Override + public void exitNameBasic(QueryLangParser.NameBasicContext ctx) { StringPath stringPath = QUser.user.name; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitNameList(QueryLangParser.NameListContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); } @Override - public void exitSurnameComparison(QueryLangParser.SurnameComparisonContext ctx) { + public void exitNameRange(QueryLangParser.NameRangeContext ctx) { + StringPath stringPath = QUser.user.name; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitSurnameBasic(QueryLangParser.SurnameBasicContext ctx) { StringPath stringPath = QUser.user.surname; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); } @Override - public void exitEmailComparison(QueryLangParser.EmailComparisonContext ctx) { + public void exitSurnameList(QueryLangParser.SurnameListContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitSurnameRange(QueryLangParser.SurnameRangeContext ctx) { + StringPath stringPath = QUser.user.surname; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + } + + @Override + public void exitEmailBasic(QueryLangParser.EmailBasicContext ctx) { StringPath stringPath = QUser.user.email; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; String string = getStringValue(ctx.stringComparison().STRING().getText()); - setMongoQuery(ctx, buildStringPredicate(stringPath, op, string, not)); + setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); + } + + @Override + public void exitEmailList(QueryLangParser.EmailListContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitEmailRange(QueryLangParser.EmailRangeContext ctx) { + StringPath stringPath = QUser.user.email; + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); } @Override @@ -644,7 +967,32 @@ public void exitDataString(QueryLangParser.DataStringContext ctx) { String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op, string, not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".textValue", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".textValue", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".textValue", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); this.searchWithElastic = true; } @@ -657,7 +1005,44 @@ public void exitDataNumber(QueryLangParser.DataNumberContext ctx) { String number = ctx.numberComparison().number.getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op, number, not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".numberValue", op.getType(), number, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List<String> stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".numberValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".numberValue", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); this.searchWithElastic = true; } @@ -670,7 +1055,7 @@ public void exitDataDate(QueryLangParser.DataDateContext ctx) { LocalDateTime localDateTime = toDateTime(ctx.dateComparison().DATE().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); this.searchWithElastic = true; } @@ -683,7 +1068,47 @@ public void exitDataDatetime(QueryLangParser.DataDatetimeContext ctx) { LocalDateTime localDateTime = toDateTime(ctx.dateTimeComparison().DATETIME().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op, String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".timestampValue", op.getType(), String.valueOf(Timestamp.valueOf(localDateTime).getTime()), not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inListDateComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListDateComparison().dateList() != null ? ctx.inListDateComparison().dateList().DATE() : ctx.inListDateComparison().dateTimeList().DATETIME(); + List<String> stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).map(dateAsString -> { + LocalDateTime localDateTime = toDateTime(dateAsString); + return String.valueOf(Timestamp.valueOf(localDateTime).getTime()); + }).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".timestampValue", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + String fieldId = ctx.dataValue().fieldId.getText(); + boolean not = ctx.inRangeDateComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + LocalDateTime leftDateTime; + LocalDateTime rightDateTime; + if (ctx.inRangeDateComparison().dateRange() != null) { + leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); + } else { + leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); + rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".timestampValue", String.valueOf(Timestamp.valueOf(leftDateTime).getTime()), leftEndpointOpen, String.valueOf(Timestamp.valueOf(rightDateTime).getTime()), rightEndpointOpen, not)); this.searchWithElastic = true; } @@ -696,12 +1121,12 @@ public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { String booleanValue = ctx.booleanComparison().BOOLEAN().getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op, booleanValue, not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".booleanValue", op.getType(), booleanValue, not)); this.searchWithElastic = true; } @Override - public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { String fieldId = ctx.dataOptions().fieldId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); @@ -709,12 +1134,37 @@ public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonCont String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op, string, not)); + setElasticQuery(ctx, buildElasticQuery("dataSet." + fieldId + ".options", op.getType(), string, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("dataSet." + fieldId + ".options", stringList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + String fieldId = ctx.dataOptions().fieldId.getText(); + boolean not = ctx.inRangeStringComparison().NOT() != null; + boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); + String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("dataSet." + fieldId + ".options", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); this.searchWithElastic = true; } @Override - public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { String placeId = ctx.places().placeId.getText(); Token op = ctx.numberComparison().op; checkOp(ComparisonType.NUMBER, op); @@ -722,7 +1172,44 @@ public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { String numberValue = ctx.numberComparison().number.getText(); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op, numberValue, not)); + setElasticQuery(ctx, buildElasticQuery("places." + placeId + ".marking", op.getType(), numberValue, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesList(QueryLangParser.PlacesListContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inListNumberComparison().NOT() != null; + List<TerminalNode> terminalNodeList = ctx.inListNumberComparison().intList() != null ? ctx.inListNumberComparison().intList().INT() : ctx.inListNumberComparison().doubleList().DOUBLE(); + List<String> stringNumberList = terminalNodeList.stream().map(TerminalNode::getText).collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("places." + placeId + ".marking", stringNumberList, not)); + this.searchWithElastic = true; + } + + @Override + public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { + String placeId = ctx.places().placeId.getText(); + boolean not = ctx.inRangeNumberComparison().NOT() != null; + boolean leftEndpointOpen; + boolean rightEndpointOpen; + String leftNumberAsString; + String rightNumberAsString; + if (ctx.inRangeNumberComparison().intRange() != null) { + leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); + } else { + leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); + rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); + } + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInRange("places." + placeId + ".marking", leftNumberAsString, leftEndpointOpen, rightNumberAsString, rightEndpointOpen, not)); this.searchWithElastic = true; } @@ -735,7 +1222,7 @@ public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext State state = ctx.state.getType() == QueryLangParser.ENABLED ? State.ENABLED : State.DISABLED; setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op, state.toString(), not)); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".state", op.getType(), state.toString(), not)); this.searchWithElastic = true; } @@ -748,7 +1235,7 @@ public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonConte String string = getStringValue(ctx.stringComparison().STRING().getText()); setMongoQuery(ctx, null); - setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op, string, not)); + setElasticQuery(ctx, buildElasticQuery("tasks." + taskId + ".userId", op.getType(), string, not)); this.searchWithElastic = true; } diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java index 219c973bd3f..239a9425bd7 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -332,7 +332,17 @@ public void exitUserCondition(QueryLangParser.UserConditionContext ctx) { } @Override - public void exitPlacesComparison(QueryLangParser.PlacesComparisonContext ctx) { + public void exitPlacesBasic(QueryLangParser.PlacesBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesList(QueryLangParser.PlacesListContext ctx) { + searchWithElastic = true; + } + + @Override + public void enterPlacesRange(QueryLangParser.PlacesRangeContext ctx) { searchWithElastic = true; } @@ -372,10 +382,49 @@ public void exitDataBoolean(QueryLangParser.DataBooleanContext ctx) { } @Override - public void enterDataOptionsComparison(QueryLangParser.DataOptionsComparisonContext ctx) { + public void exitDataDateList(QueryLangParser.DataDateListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberList(QueryLangParser.DataNumberListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringList(QueryLangParser.DataStringListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsBasic(QueryLangParser.DataOptionsBasicContext ctx) { this.searchWithElastic = true; } + @Override + public void exitDataOptionsList(QueryLangParser.DataOptionsListContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { + searchWithElastic = true; + } @Override public void exitPaging(QueryLangParser.PagingContext ctx) { diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 7a12c40c61d..fc6aa609c2e 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -40,9 +40,8 @@ userConditionGroup: userCondition # userConditionGroupBasic userCondition: userComparisons SPACE? ; // delimeter -delimeter: WHERE_DELIMETER | COLON_DELIMETER ; -WHERE_DELIMETER: SPACE W H E R E SPACE ; -COLON_DELIMETER: SPACE? ':' SPACE ; +delimeter: SPACE WHERE SPACE | SPACE? ':' SPACE ; +WHERE: W H E R E ; // paging paging: PAGE SPACE pageNum=INT (SPACE SIZE SPACE pageSize=INT)? SPACE?; @@ -102,7 +101,7 @@ processComparisons: idComparison ; caseComparisons: idComparison - | processIdComparison + | processIdObjIdComparison | processIdentifierComparison | titleComparison | creationDateComparison @@ -133,47 +132,100 @@ userComparisons: idComparison // attribute comparisons idComparison: ID SPACE objectIdComparison ; -titleComparison: TITLE SPACE stringComparison ; -identifierComparison: IDENTIFIER SPACE stringComparison ; -versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER ; -creationDateComparison: CREATION_DATE SPACE dateComparison # cdDate - | CREATION_DATE SPACE dateTimeComparison # cdDateTime +titleComparison: TITLE SPACE stringComparison # titleBasic + | TITLE SPACE inListStringComparison # titleList + | TITLE SPACE inRangeStringComparison # titleRange + ; +identifierComparison: IDENTIFIER SPACE stringComparison # identifierBasic + | IDENTIFIER SPACE inListStringComparison # identifierList + | IDENTIFIER SPACE inRangeStringComparison # identifierRange + ; +versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER # versionBasic + | VERSION SPACE inListVersionComparison # versionListCmp + | VERSION SPACE inRangeVersionComparison # versionRangeCmp + ; // todo NAE-1997: in list/in range?? +creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic + | CREATION_DATE SPACE dateTimeComparison # cdDateTimeBasic + | CREATION_DATE SPACE inListDateComparison # cdDateList + | CREATION_DATE SPACE inRangeDateComparison # cdDateRange ; -processIdComparison: PROCESS_ID SPACE stringComparison ; -processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison ; -authorComparison: AUTHOR SPACE stringComparison ; -transitionIdComparison: TRANSITION_ID SPACE stringComparison ; -stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; -userIdComparison: USER_ID SPACE stringComparison ; -caseIdComparison: CASE_ID SPACE stringComparison ; -lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDate - | LAST_ASSIGN SPACE dateTimeComparison # laDateTime +processIdComparison: PROCESS_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison ; +processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic + | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList + | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange + ; +authorComparison: AUTHOR SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic + | TRANSITION_ID SPACE inListStringComparison # transitionIdList + | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange + ; +stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; // todo NAE-1997: in list/in range?? only 2 values +userIdComparison: USER_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +caseIdComparison: CASE_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic + | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic + | LAST_ASSIGN SPACE inListDateComparison # laDateList + | LAST_ASSIGN SPACE inRangeDateComparison # laDateRange ; -lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDate - | LAST_FINISH SPACE dateTimeComparison # lfDateTime +lastFinishComparison: LAST_FINISH SPACE dateComparison # lfDateBasic + | LAST_FINISH SPACE dateTimeComparison # lfDateTimeBasic + | LAST_FINISH SPACE inListDateComparison # lfDateList + | LAST_FINISH SPACE inRangeDateComparison # lfDateRange ; -nameComparison: NAME SPACE stringComparison ; -surnameComparison: SURNAME SPACE stringComparison ; -emailComparison: EMAIL SPACE stringComparison ; -dataValueComparison: dataValue SPACE stringComparison # dataString +nameComparison: NAME SPACE stringComparison # nameBasic + | NAME SPACE inListStringComparison # nameList + | NAME SPACE inRangeStringComparison # nameRange + ; +surnameComparison: SURNAME SPACE stringComparison # surnameBasic + | SURNAME SPACE inListStringComparison # surnameList + | SURNAME SPACE inRangeStringComparison # surnameRange + ; +emailComparison: EMAIL SPACE stringComparison # emailBasic + | EMAIL SPACE inListStringComparison # emailList + | EMAIL SPACE inRangeStringComparison # emailRange + ; +dataValueComparison: dataValue SPACE stringComparison # dataString // todo NAE-1997: how to translate to elastic query?? | dataValue SPACE numberComparison # dataNumber | dataValue SPACE dateComparison # dataDate | dataValue SPACE dateTimeComparison # dataDatetime | dataValue SPACE booleanComparison # dataBoolean + | dataValue SPACE inListStringComparison # dataStringList + | dataValue SPACE inListNumberComparison # dataNumberList + | dataValue SPACE inListDateComparison # dataDateList + | dataValue SPACE inRangeStringComparison # dataStringRange + | dataValue SPACE inRangeNumberComparison # dataNumberRange + | dataValue SPACE inRangeDateComparison # dataDateRange ; -dataOptionsComparison: dataOptions SPACE stringComparison ; -placesComparison: places SPACE numberComparison ; -tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; -tasksUserIdComparison: tasksUserId SPACE stringComparison ; +dataOptionsComparison: dataOptions SPACE stringComparison # dataOptionsBasic + | dataOptions SPACE inListStringComparison # dataOptionsList + | dataOptions SPACE inRangeStringComparison # dataOptionsRange + ; +placesComparison: places SPACE numberComparison # placesBasic + | places SPACE inListNumberComparison # placesList + | places SPACE inRangeNumberComparison # placesRange + ; +tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; // todo NAE-1997: in list/in range?? only 2 values +tasksUserIdComparison: tasksUserId SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison // basic comparisons objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; -stringComparison: (NOT SPACE?)? op=(EQ | CONTAINS) SPACE STRING ; +stringComparison: (NOT SPACE?)? op=(EQ | CONTAINS | LT | GT | LTE | GTE) SPACE STRING ; numberComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE number=(INT | DOUBLE) ; dateComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATE ; dateTimeComparison: (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE DATETIME ; booleanComparison: (NOT SPACE?)? op=EQ SPACE BOOLEAN ; +// in list/in range comparisons +inListStringComparison: (NOT SPACE?)? op=IN SPACE stringList ; +inListNumberComparison: (NOT SPACE?)? op=IN SPACE (intList | doubleList) ; +inListDateComparison: (NOT SPACE?)? op=IN SPACE (dateList | dateTimeList) ; +inListVersionComparison: (NOT SPACE?)? op=IN SPACE versionList ; +inRangeStringComparison: (NOT SPACE?)? op=IN SPACE stringRange ; +inRangeNumberComparison: (NOT SPACE?)? op=IN SPACE (intRange | doubleRange) ; +inRangeDateComparison: (NOT SPACE?)? op=IN SPACE (dateRange | dateTimeRange) ; +inRangeVersionComparison: (NOT SPACE?)? op=IN SPACE versionRange ; + // special attribute rules dataValue: DATA '.' fieldId=JAVA_ID '.' VALUE ; dataOptions: DATA '.' fieldId=JAVA_ID '.' OPTIONS ; @@ -191,6 +243,7 @@ GT: G T | '>' ; LTE: L T E | '<=' ; GTE: G T E | '>=' ; CONTAINS: C O N T A I N S | '~'; +IN: I N ; // resurces CASE: C A S E ; @@ -239,7 +292,18 @@ ASC: A S C ; DESC: D E S C ; // basic types -LIST: '[' SPACE? ((STRING | NUMBER) SPACE? (',' SPACE? (STRING | NUMBER) SPACE? )* )? SPACE? ']' ; +stringList: '(' SPACE? (STRING SPACE? (',' SPACE? STRING SPACE? )* )? SPACE? ')' ; +intList: '(' SPACE? (INT SPACE? (',' SPACE? INT SPACE? )* )? SPACE? ')' ; +doubleList: '(' SPACE? (DOUBLE SPACE? (',' SPACE? DOUBLE SPACE? )* )? SPACE? ')' ; +dateList: '(' SPACE? (DATE SPACE? (',' SPACE? DATE SPACE? )* )? SPACE? ')' ; +dateTimeList: '(' SPACE? (DATETIME SPACE? (',' SPACE? DATETIME SPACE? )* )? SPACE? ')' ; +versionList: '(' SPACE? (VERSION_NUMBER SPACE? (',' SPACE? VERSION_NUMBER SPACE? )* )? SPACE? ')' ; +stringRange: leftEndpoint=('(' | '<') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | '>') ; +intRange: leftEndpoint=('(' | '<') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | '>') ; +doubleRange: leftEndpoint=('(' | '<') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | '>') ; +dateRange: leftEndpoint=('(' | '<') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | '>') ; +dateTimeRange: leftEndpoint=('(' | '<') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | '>') ; +versionRange: leftEndpoint=('(' | '<') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | '>') ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? INT: DIGIT+ ; DOUBLE: DIGIT+ '.' DIGIT+ ; diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index 40a13ca51f2..f67ecf90829 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -10,7 +10,9 @@ import com.netgrif.application.engine.petrinet.domain.version.QVersion; import com.netgrif.application.engine.petrinet.domain.version.Version; import com.netgrif.application.engine.search.enums.ComparisonType; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; import com.querydsl.core.types.dsl.DateTimePath; import com.querydsl.core.types.dsl.StringPath; import lombok.extern.slf4j.Slf4j; @@ -27,6 +29,7 @@ import java.time.format.DateTimeParseException; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Slf4j public class SearchUtils { @@ -86,6 +89,9 @@ public class SearchUtils { "email", "email" ); + public static final String LEFT_OPEN_ENDPOINT = "("; + public static final String RIGHT_OPEN_ENDPOINT = ")"; + public static String toDateString(LocalDate localDate) { return localDate.format(DateTimeFormatter.ISO_LOCAL_DATE); } @@ -190,9 +196,9 @@ public static void checkOp(ComparisonType type, Token op) { } } - public static Predicate buildObjectIdPredicate(QObjectId qObjectId, Token op, ObjectId objectId, boolean not) { - if (op.getType() != QueryLangParser.EQ) { - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for id comparison"); + public static Predicate buildObjectIdPredicate(QObjectId qObjectId, int op, ObjectId objectId, boolean not) { + if (op != QueryLangParser.EQ) { + throw new UnsupportedOperationException("Operator is not available for id comparison"); } Predicate predicate = qObjectId.eq(objectId); @@ -202,19 +208,31 @@ public static Predicate buildObjectIdPredicate(QObjectId qObjectId, Token op, Ob return predicate; } - public static Predicate buildStringPredicate(StringPath stringPath, Token op, String string, boolean not) { + public static Predicate buildStringPredicate(StringPath stringPath, int op, String string, boolean not) { Predicate predicate = null; - switch (op.getType()) { + switch (op) { case QueryLangParser.EQ: predicate = stringPath.eq(string); break; case QueryLangParser.CONTAINS: predicate = stringPath.contains(string); break; + case QueryLangParser.LT: + predicate = stringPath.lt(string); + break; + case QueryLangParser.LTE: + predicate = stringPath.loe(string); + break; + case QueryLangParser.GT: + predicate = stringPath.gt(string); + break; + case QueryLangParser.GTE: + predicate = stringPath.goe(string); + break; } if (predicate == null) { - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for string comparison"); + throw new UnsupportedOperationException("Operator is not available for string comparison"); } if (not) { @@ -223,7 +241,21 @@ public static Predicate buildStringPredicate(StringPath stringPath, Token op, St return predicate; } - public static Predicate buildVersionPredicate(Token op, String versionString, boolean not) { + public static Predicate buildStringPredicateInList(StringPath stringPath, List<String> values, boolean not) { + Predicate predicate = stringPath.in(values); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildStringPredicateInRange(StringPath stringPath, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? stringPath.gt(leftValue) : stringPath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? stringPath.lt(rightValue) : stringPath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicate(int op, String versionString, boolean not) { String[] versionNumber = versionString.split("\\."); long major = Long.parseLong(versionNumber[0]); long minor = Long.parseLong(versionNumber[1]); @@ -232,7 +264,7 @@ public static Predicate buildVersionPredicate(Token op, String versionString, bo QVersion qVersion = QPetriNet.petriNet.version; Predicate predicate = null; - switch (op.getType()) { + switch (op) { case QueryLangParser.EQ: predicate = qVersion.eq(new Version(major, minor, patch)); break; @@ -242,10 +274,9 @@ public static Predicate buildVersionPredicate(Token op, String versionString, bo .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))); break; case QueryLangParser.GTE: - predicate = qVersion.major.gt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.gt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.gt(patch)))) - .or(qVersion.eq(new Version(major, minor, patch))); + predicate = qVersion.major.goe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.goe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.goe(patch)))); break; case QueryLangParser.LT: predicate = qVersion.major.lt(major) @@ -253,15 +284,14 @@ public static Predicate buildVersionPredicate(Token op, String versionString, bo .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))); break; case QueryLangParser.LTE: - predicate = qVersion.major.lt(major) - .or(qVersion.major.eq(major).and(qVersion.minor.lt(minor))) - .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.lt(patch)))) - .or(qVersion.eq(new Version(major, minor, patch))); + predicate = qVersion.major.loe(major) + .or(qVersion.major.eq(major).and(qVersion.minor.loe(minor))) + .or(qVersion.major.eq(major).and(qVersion.minor.eq(minor).and(qVersion.patch.loe(patch)))); break; } if (predicate == null) { - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for version comparison"); + throw new UnsupportedOperationException("Operator is not available for version comparison"); } if (not) { @@ -270,9 +300,34 @@ public static Predicate buildVersionPredicate(Token op, String versionString, bo return predicate; } - public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateTimePath, Token op, LocalDateTime localDateTime, boolean not) { + public static Predicate buildVersionPredicateInList(List<String> values, boolean not) { + List<Version> versions = values.stream().map(stringVersion -> { + String[] versionNumber = stringVersion.split("\\."); + long major = Long.parseLong(versionNumber[0]); + long minor = Long.parseLong(versionNumber[1]); + long patch = Long.parseLong(versionNumber[2]); + + return new Version(major, minor, patch); + }).collect(Collectors.toList()); + + Predicate predicate = QPetriNet.petriNet.version.in(versions); + return not ? predicate.not() : predicate; + } + + public static Predicate buildVersionPredicateInRange(String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + Predicate leftExpression = buildVersionPredicate(leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false); + Predicate rightExpression = buildVersionPredicate(rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false); + + BooleanBuilder predicate = new BooleanBuilder(); + predicate.and(leftExpression); + predicate.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateTimePath, int op, LocalDateTime localDateTime, boolean not) { Predicate predicate = null; - switch (op.getType()) { + switch (op) { case QueryLangParser.EQ: predicate = dateTimePath.eq(localDateTime); break; @@ -280,18 +335,18 @@ public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateT predicate = dateTimePath.lt(localDateTime); break; case QueryLangParser.LTE: - predicate = dateTimePath.lt(localDateTime).or(dateTimePath.eq(localDateTime)); + predicate = dateTimePath.loe(localDateTime); break; case QueryLangParser.GT: predicate = dateTimePath.gt(localDateTime); break; case QueryLangParser.GTE: - predicate = dateTimePath.gt(localDateTime).or(dateTimePath.eq(localDateTime)); + predicate = dateTimePath.goe(localDateTime); break; } if (predicate == null) { - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for date/datetime comparison"); + throw new UnsupportedOperationException("Operator is not available for date/datetime comparison"); } if (not) { @@ -300,10 +355,26 @@ public static Predicate buildDateTimePredicate(DateTimePath<LocalDateTime> dateT return predicate; } - public static String buildElasticQuery(String attribute, Token op, String value, boolean not) { + public static Predicate buildDateTimePredicateInList(DateTimePath<LocalDateTime> dateTimePath, List<String> values, boolean not) { + List<LocalDateTime> dateTimes = values.stream().map(SearchUtils::toDateTime).collect(Collectors.toList()); + Predicate predicate = dateTimePath.in(dateTimes); + + return not ? predicate.not() : predicate; + } + + public static Predicate buildDateTimePredicateInRange(DateTimePath<LocalDateTime> dateTimePath, LocalDateTime leftValue, boolean leftEndpointOpen, LocalDateTime rightValue, boolean rightEndpointOpen, boolean not) { + BooleanExpression leftExpression = leftEndpointOpen ? dateTimePath.gt(leftValue) : dateTimePath.goe(leftValue); + BooleanExpression rightExpression = rightEndpointOpen ? dateTimePath.lt(rightValue) : dateTimePath.loe(rightValue); + Predicate predicate = leftExpression.and(rightExpression); + + return not ? predicate.not() : predicate; + } + + public static String buildElasticQuery(String attribute, int op, String value, boolean not) { String query = null; - switch (op.getType()) { + switch (op) { case QueryLangParser.EQ: + case QueryLangParser.IN: query = attribute + ":" + value; break; case QueryLangParser.LT: @@ -324,7 +395,7 @@ public static String buildElasticQuery(String attribute, Token op, String value, } if (query == null) { - throw new UnsupportedOperationException("Operator " + op.getText() + " is not available for elastic comparison"); + throw new UnsupportedOperationException("Operator is not available for elastic comparison"); } if (not) { @@ -332,4 +403,18 @@ public static String buildElasticQuery(String attribute, Token op, String value, } return query; } + + public static String buildElasticQueryInList(String attribute, List<String> values, boolean not) { + String valuesQuery = "(" + String.join(" OR ", values) + ")"; + return buildElasticQuery(attribute, QueryLangParser.IN, valuesQuery, not); + } + + public static String buildElasticQueryInRange(String attribute, String leftValue, boolean leftEndpointOpen, String rightValue, boolean rightEndpointOpen, boolean not) { + String query = "(" + + buildElasticQuery(attribute, leftEndpointOpen ? QueryLangParser.GT : QueryLangParser.GTE, leftValue, false) + + " AND " + + buildElasticQuery(attribute, rightEndpointOpen ? QueryLangParser.LT : QueryLangParser.LTE, rightValue, false) + + ")"; + return not ? "NOT " + query : query; + } } From 2d334492ba7cf5fae6428e478354846a331c7c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 5 Mar 2025 11:37:50 +0100 Subject: [PATCH 21/27] [NAE-1997] Query language - add tests for in range\in list operators --- .../engine/search/QueryLangEvaluator.java | 36 +- .../engine/search/QueryLangTest.java | 390 +++++++++--------- 2 files changed, 218 insertions(+), 208 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 942e54c581a..44131af385f 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -445,7 +445,7 @@ public void exitTitleRange(QueryLangParser.TitleRangeContext ctx) { StringPath stringPath; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -491,7 +491,7 @@ public void exitIdentifierRange(QueryLangParser.IdentifierRangeContext ctx) { StringPath stringPath = QPetriNet.petriNet.identifier; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -519,7 +519,7 @@ public void exitVersionListCmp(QueryLangParser.VersionListCmpContext ctx) { public void exitVersionRangeCmp(QueryLangParser.VersionRangeCmpContext ctx) { boolean not = ctx.inRangeVersionComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeVersionComparison().versionRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeVersionComparison().versionRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(0).getText()); String rightString = getStringValue(ctx.inRangeVersionComparison().versionRange().VERSION_NUMBER(1).getText()); @@ -606,12 +606,12 @@ public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { LocalDateTime rightDateTime; if (ctx.inRangeDateComparison().dateRange() != null) { leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); } else { leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); } @@ -678,7 +678,7 @@ public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeCon StringPath stringPath = QCase.case$.processIdentifier; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -720,7 +720,7 @@ public void exitTransitionIdRange(QueryLangParser.TransitionIdRangeContext ctx) StringPath stringPath = QTask.task.transitionId; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -799,12 +799,12 @@ public void exitLaDateRange(QueryLangParser.LaDateRangeContext ctx) { LocalDateTime rightDateTime; if (ctx.inRangeDateComparison().dateRange() != null) { leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); } else { leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); } @@ -852,12 +852,12 @@ public void exitLfDateRange(QueryLangParser.LfDateRangeContext ctx) { LocalDateTime rightDateTime; if (ctx.inRangeDateComparison().dateRange() != null) { leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); } else { leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); } @@ -889,7 +889,7 @@ public void exitNameRange(QueryLangParser.NameRangeContext ctx) { StringPath stringPath = QUser.user.name; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -920,7 +920,7 @@ public void exitSurnameRange(QueryLangParser.SurnameRangeContext ctx) { StringPath stringPath = QUser.user.surname; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -951,7 +951,7 @@ public void exitEmailRange(QueryLangParser.EmailRangeContext ctx) { StringPath stringPath = QUser.user.email; boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -987,7 +987,7 @@ public void exitDataStringRange(QueryLangParser.DataStringRangeContext ctx) { String fieldId = ctx.dataValue().fieldId.getText(); boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); @@ -1097,12 +1097,12 @@ public void exitDataDateRange(QueryLangParser.DataDateRangeContext ctx) { LocalDateTime rightDateTime; if (ctx.inRangeDateComparison().dateRange() != null) { leftEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateRange().DATE(1).getText()); } else { leftEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeDateComparison().dateTimeRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(0).getText()); rightDateTime = toDateTime(ctx.inRangeDateComparison().dateTimeRange().DATETIME(1).getText()); } @@ -1154,7 +1154,7 @@ public void exitDataOptionsRange(QueryLangParser.DataOptionsRangeContext ctx) { String fieldId = ctx.dataOptions().fieldId.getText(); boolean not = ctx.inRangeStringComparison().NOT() != null; boolean leftEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + boolean rightEndpointOpen = ctx.inRangeStringComparison().stringRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); String leftString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(0).getText()); String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 2ddfa632332..3db8b9ba716 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -7,7 +7,10 @@ import com.netgrif.application.engine.petrinet.domain.version.Version; import com.netgrif.application.engine.search.utils.MongoDbUtils; import com.netgrif.application.engine.workflow.domain.*; +import com.querydsl.core.BooleanBuilder; import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.DateTimePath; +import com.querydsl.core.types.dsl.StringPath; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.bson.types.ObjectId; @@ -27,7 +30,6 @@ import java.util.List; import static com.netgrif.application.engine.search.utils.SearchUtils.evaluateQuery; -import static com.netgrif.application.engine.search.utils.SearchUtils.explainQuery; @Slf4j @SpringBootTest @@ -50,15 +52,7 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // identifier comparison - actual = evaluateQuery("process: identifier eq 'test'").getFullMongoQuery(); - expected = QPetriNet.petriNet.identifier.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("process: identifier contains 'test'").getFullMongoQuery(); - expected = QPetriNet.petriNet.identifier.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "process", "identifier", QPetriNet.petriNet.identifier); // version comparison actual = evaluateQuery("process: version eq 1.1.1").getFullMongoQuery(); @@ -74,10 +68,9 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); actual = evaluateQuery("process: version lte 1.1.1").getFullMongoQuery(); - expected = QPetriNet.petriNet.version.major.lt(1) - .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.lt(1))) - .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.lt(1)))) - .or(QPetriNet.petriNet.version.eq(new Version(1, 1, 1))); + expected = QPetriNet.petriNet.version.major.loe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.loe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.loe(1)))); compareMongoQueries(mongoDbUtils, actual, expected); @@ -89,51 +82,66 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); actual = evaluateQuery("process: version gte 1.1.1").getFullMongoQuery(); - expected = QPetriNet.petriNet.version.major.gt(1) - .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) - .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1)))) - .or(QPetriNet.petriNet.version.eq(new Version(1, 1, 1))); + expected = QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1)))); compareMongoQueries(mongoDbUtils, actual, expected); - // title comparison - actual = evaluateQuery("process: title eq 'test'").getFullMongoQuery(); - expected = QPetriNet.petriNet.title.defaultValue.eq("test"); + Version v1 = new Version(1, 1, 1); + Version v2 = new Version(2, 2, 2); + Version v3 = new Version(3, 3, 3); + actual = evaluateQuery("process: version in (1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: title contains 'test'").getFullMongoQuery(); - expected = QPetriNet.petriNet.title.defaultValue.contains("test"); + actual = evaluateQuery("process: version not in (1.1.1, 2.2.2, 3.3.3)").getFullMongoQuery(); + expected = QPetriNet.petriNet.version.in(List.of(v1, v2, v3)).not(); compareMongoQueries(mongoDbUtils, actual, expected); - // creationDate comparison - actual = evaluateQuery("process: creationDate eq 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("process: creationDate lt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QPetriNet.petriNet.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + actual = evaluateQuery("process: version in (1.1.1 : 2.2.2)").getFullMongoQuery(); + BooleanBuilder builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.lt(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.lt(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.lt(2))))); + expected = builder; compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: creationDate lte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QPetriNet.petriNet.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + actual = evaluateQuery("process: version in <1.1.1 : 2.2.2>").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.goe(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.goe(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder; compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: creationDate gt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QPetriNet.petriNet.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + actual = evaluateQuery("process: version not in (1.1.1 : 2.2.2>").getFullMongoQuery(); + builder = new BooleanBuilder(); + builder.and(QPetriNet.petriNet.version.major.gt(1) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) + .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.eq(1L).and(QPetriNet.petriNet.version.patch.gt(1))))); + builder.and(QPetriNet.petriNet.version.major.loe(2) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.loe(2))) + .or(QPetriNet.petriNet.version.major.eq(2L).and(QPetriNet.petriNet.version.minor.eq(2L).and(QPetriNet.petriNet.version.patch.loe(2))))); + expected = builder.not(); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: creationDate gte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QPetriNet.petriNet.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QPetriNet.petriNet.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); + // title comparison + checkStringComparison(mongoDbUtils, "process", "title", QPetriNet.petriNet.title.defaultValue); - compareMongoQueries(mongoDbUtils, actual, expected); + // creationDate comparison + checkDateComparison(mongoDbUtils, "process", "creationDate", QPetriNet.petriNet.creationDate); } @Test @@ -207,57 +215,14 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); - compareMongoQueries(mongoDbUtils, actual, expected); - // processIdentifier comparison - actual = evaluateQuery("case: processIdentifier eq 'test'").getFullMongoQuery(); - expected = QCase.case$.processIdentifier.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: processIdentifier contains 'test'").getFullMongoQuery(); - expected = QCase.case$.processIdentifier.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "case", "processIdentifier", QCase.case$.processIdentifier); // title comparison - actual = evaluateQuery("case: title eq 'test'").getFullMongoQuery(); - expected = QCase.case$.title.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: title contains 'test'").getFullMongoQuery(); - expected = QCase.case$.title.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "case", "title", QCase.case$.title); // creationDate comparison - actual = evaluateQuery("case: creationDate eq 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: creationDate lt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QCase.case$.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: creationDate lte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QCase.case$.creationDate.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: creationDate gt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QCase.case$.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("case: creationDate gte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QCase.case$.creationDate.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QCase.case$.creationDate.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkDateComparison(mongoDbUtils, "case", "creationDate", QCase.case$.creationDate); // author comparison actual = evaluateQuery("case: author eq 'test'").getFullMongoQuery(); @@ -391,26 +356,10 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // transitionId comparison - actual = evaluateQuery("task: transitionId eq 'test'").getFullMongoQuery(); - expected = QTask.task.transitionId.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: transitionId contains 'test'").getFullMongoQuery(); - expected = QTask.task.transitionId.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "task", "transitionId", QTask.task.transitionId); // title comparison - actual = evaluateQuery("task: title eq 'test'").getFullMongoQuery(); - expected = QTask.task.title.defaultValue.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: title contains 'test'").getFullMongoQuery(); - expected = QTask.task.title.defaultValue.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "task", "title", QTask.task.title.defaultValue); // state comparison actual = evaluateQuery("task: state eq enabled").getFullMongoQuery(); @@ -457,60 +406,10 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // lastAssign comparison - actual = evaluateQuery("task: lastAssign eq 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastAssign lt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastAssigned.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastAssign lte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastAssigned.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastAssign gt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastAssigned.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastAssign gte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastAssigned.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QTask.task.lastAssigned.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkDateComparison(mongoDbUtils, "task", "lastAssign", QTask.task.lastAssigned); // lastFinish comparison - actual = evaluateQuery("task: lastFinish eq 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastFinish lt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastFinished.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastFinish lte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastFinished.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastFinish gt 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastFinished.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("task: lastFinish gte 2011-12-03T10:15:30").getFullMongoQuery(); - expected = QTask.task.lastFinished.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)) - .or(QTask.task.lastFinished.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30))); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkDateComparison(mongoDbUtils, "task", "lastFinish", QTask.task.lastFinished); } @Test @@ -579,37 +478,13 @@ public void testSimpleMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); // name comparison - actual = evaluateQuery("user: name eq 'test'").getFullMongoQuery(); - expected = QUser.user.name.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("user: name contains 'test'").getFullMongoQuery(); - expected = QUser.user.name.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "user", "name", QUser.user.name); // surname comparison - actual = evaluateQuery("user: surname eq 'test'").getFullMongoQuery(); - expected = QUser.user.surname.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("user: surname contains 'test'").getFullMongoQuery(); - expected = QUser.user.surname.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "user", "surname", QUser.user.surname); // email comparison - actual = evaluateQuery("user: email eq 'test'").getFullMongoQuery(); - expected = QUser.user.email.eq("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); - - actual = evaluateQuery("user: email contains 'test'").getFullMongoQuery(); - expected = QUser.user.email.contains("test"); - - compareMongoQueries(mongoDbUtils, actual, expected); + checkStringComparison(mongoDbUtils, "user", "email", QUser.user.email); } @Test @@ -1655,12 +1530,6 @@ public void testComparisonTypeFail() { Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gt 'test'")); Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: id gte 'test'")); - // string comparison - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier lt 'test'")); - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier lte 'test'")); - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier gt 'test'")); - Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: identifier gte 'test'")); - // number comparison Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: places.p1.marking contains 1")); @@ -1675,7 +1544,148 @@ public void testComparisonTypeFail() { Assertions.assertThrows(IllegalArgumentException.class, () -> evaluateQuery("case: data.field1.value gte true")); } - private void compareMongoQueries(MongoDbUtils<?> mongoDbUtils, Predicate actual, Predicate expected) { + private static void checkStringComparison(MongoDbUtils<?> mongoDbUtils, String resource, String attribute, StringPath stringPath) { + Predicate actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullMongoQuery(); + Predicate expected = stringPath.eq("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.contains("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.lt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.loe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test"); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.in(List.of("test1", "test2", "test3")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.lt("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in <'test1' : 'test2'>", resource, attribute)).getFullMongoQuery(); + expected = stringPath.goe("test1").and(stringPath.loe("test2")); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2'>", resource, attribute)).getFullMongoQuery(); + expected = stringPath.gt("test1").and(stringPath.loe("test2")).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void checkDateComparison(MongoDbUtils<?> mongoDbUtils, String resource, String attribute, DateTimePath<LocalDateTime> dateTimePath) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + Predicate actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + Predicate expected = dateTimePath.eq(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.lt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.loe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(LocalDateTime.of(2011, 12, 3, 10, 15, 30)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date1, date2, date3)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.lt(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in <2011-12-03T10:15:30 : 2011-12-03T11:15:30>", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date1).and(dateTimePath.loe(date2)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30>", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date1).and(dateTimePath.loe(date2)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.in(List.of(date4, date5, date6)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.lt(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s in <2011-12-03 : 2011-12-03>", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.goe(date4).and(dateTimePath.loe(date5)); + + compareMongoQueries(mongoDbUtils, actual, expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03>", resource, attribute)).getFullMongoQuery(); + expected = dateTimePath.gt(date4).and(dateTimePath.loe(date5)).not(); + + compareMongoQueries(mongoDbUtils, actual, expected); + } + + private static void compareMongoQueries(MongoDbUtils<?> mongoDbUtils, Predicate actual, Predicate expected) { Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); From e71db6d99003f752a93f1f1ebb8979205f0cd6da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 5 Mar 2025 13:05:36 +0100 Subject: [PATCH 22/27] [NAE-1997] Query language - add more tests for pagination, sorting, in list, in range operators --- .../elastic/service/ElasticCaseService.java | 16 -- .../engine/search/SearchService.java | 3 - .../engine/search/SearchProcessTest.java | 124 +++++++++++ .../petriNets/search/search_test3.xml | 207 ++++++++++++++++++ 4 files changed, 331 insertions(+), 19 deletions(-) create mode 100644 src/test/resources/petriNets/search/search_test3.xml diff --git a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java index f657496a6a9..bd7fc69ac29 100644 --- a/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java +++ b/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java @@ -156,22 +156,6 @@ public long count(List<CaseSearchRequest> requests, LoggedUser user, Locale loca } } - // todo NAE-1997: implement exists -> now it only supports exists by id, we need exists + query -// @Override -// public boolean exists(List<CaseSearchRequest> requests, LoggedUser user, Locale locale, Boolean isIntersection) { -// if (requests == null) { -// throw new IllegalArgumentException("Request can not be null!"); -// } -// -// LoggedUser loggedOrImpersonated = user.getSelfOrImpersonated(); -// NativeSearchQuery query = buildQuery(requests, loggedOrImpersonated, new FullPageRequest(), locale, isIntersection); -// if (query != null) { -// return template.exists(query, ElasticCase.class); -// } else { -// return false; -// } -// } - public String findUriNodeId(Case aCase) { if (aCase == null) { return null; diff --git a/src/main/java/com/netgrif/application/engine/search/SearchService.java b/src/main/java/com/netgrif/application/engine/search/SearchService.java index 2910b9874e2..f78ee099b06 100644 --- a/src/main/java/com/netgrif/application/engine/search/SearchService.java +++ b/src/main/java/com/netgrif/application/engine/search/SearchService.java @@ -4,7 +4,6 @@ import com.netgrif.application.engine.auth.service.interfaces.IUserService; import com.netgrif.application.engine.elastic.service.interfaces.IElasticCaseService; import com.netgrif.application.engine.elastic.web.requestbodies.CaseSearchRequest; -import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.repositories.PetriNetRepository; import com.netgrif.application.engine.search.interfaces.ISearchService; import com.netgrif.application.engine.search.utils.SearchUtils; @@ -15,7 +14,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.i18n.LocaleContextHolder; -import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; @@ -65,7 +63,6 @@ private List<Case> findCasesElastic(String elasticQuery, Pageable pageable) { } private boolean existsCasesElastic(String elasticQuery) { - // todo NAE-1997: implement exists to elasticCaseService return countCasesElastic(elasticQuery) > 0; } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java index 91c573ab88e..e498634099e 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -86,9 +86,24 @@ private void comparePetriNets(List<PetriNet> actual, List<PetriNet> expected) { assert actualStringIds.containsAll(expectedStringIds); } + private void comparePetriNetsInOrder(List<PetriNet> actual, List<PetriNet> expected) { + List<String> actualStringIds = actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + + assert actualStringIds.containsAll(expectedStringIds); + + int lastIndex = -1; + for (String expectedId : expectedStringIds) { + int currentIndex = actualStringIds.indexOf(expectedId); + assert currentIndex > lastIndex; + lastIndex = currentIndex; + } + } + @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); + PetriNet net2 = importPetriNet("search/search_test.xml", VersionType.MAJOR); String query = String.format("process: id eq '%s'", net.getStringId()); @@ -98,6 +113,14 @@ public void testSearchById() { Object process = searchService.search(query); comparePetriNets(convertToPetriNet(process), net); + + query = String.format("processes: identifier eq '%s' sort by id", net.getIdentifier()); + Object processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, net2)); + + query = String.format("processes: identifier eq '%s' sort by id desc", net.getIdentifier()); + processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net)); } @Test @@ -105,6 +128,7 @@ public void testSearchByIdentifier() { PetriNet net = importPetriNet("search/search_test.xml", VersionType.MAJOR); PetriNet netNewer = importPetriNet("search/search_test.xml", VersionType.MAJOR); PetriNet net2 = importPetriNet("search/search_test2.xml", VersionType.MAJOR); + PetriNet net3 = importPetriNet("search/search_test3.xml", VersionType.MAJOR); String query = String.format("process: identifier eq '%s'", net.getIdentifier()); String queryMore = String.format("processes: identifier eq '%s'", net.getIdentifier()); @@ -117,6 +141,25 @@ public void testSearchByIdentifier() { Object processes = searchService.search(queryMore); comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + + // in list + String queryInList = String.format("processes: identifier in ('%s', '%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net3.getIdentifier()); + processes = searchService.search(queryInList); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2, net3)); + + // in range + String queryInRange = String.format("processes: identifier in <'%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + processes = searchService.search(queryInRange); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + + // sort + queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier", net.getIdentifier(), net2.getIdentifier()); + processes = searchService.search(queryMore); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + + queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier desc", net.getIdentifier(), net2.getIdentifier()); + processes = searchService.search(queryMore); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net, netNewer)); } @Test @@ -165,6 +208,25 @@ public void testSearchByVersion() { processes = searchService.search(queryGte); comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + + // in list + String queryInList = String.format("processes: identifier eq '%s' and version in (%s, %s, %s)", net.getIdentifier(), "1.0.0", "1.0.1", "1.1.0"); + processes = searchService.search(queryInList); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor)); + + // in range + String queryInRange = String.format("processes: identifier eq '%s' and version in <%s : %s)", net.getIdentifier(), "1.0.0", "1.1.0"); + processes = searchService.search(queryInRange); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch)); + + // sort + String query = String.format("processes: identifier eq '%s' sort by version", net.getIdentifier()); + processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + + query = String.format("processes: identifier eq '%s' sort by version desc", net.getIdentifier()); + processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(netNewerMajor, netNewerMinor, netNewerPatch, net)); } @Test @@ -191,6 +253,25 @@ public void testSearchByTitle() { Object processes = searchService.search(queryMore); comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + + // in list + String queryInList = String.format("processes: identifier in ('%s', '%s') and title in ('%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); + processes = searchService.search(queryInList); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + + // in range + String queryInRange = String.format("processes: identifier in ('%s', '%s') and title in <'%s' : '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); + processes = searchService.search(queryInRange); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + + // sort + queryMore = String.format("processes: identifier in ('%s', '%s') sort by title", net.getIdentifier(), net2.getIdentifier()); + processes = searchService.search(queryMore); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + + queryMore = String.format("processes: identifier in ('%s', '%s') sort by title desc", net.getIdentifier(), net2.getIdentifier()); + processes = searchService.search(queryMore); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net, netNewer)); } @Test @@ -234,6 +315,49 @@ public void testSearchByCreationDate() { processes = searchService.search(queryGte); comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); + + // in list + String queryInList = String.format("processes: identifier eq '%s' and creationDate in (%s, %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); + processes = searchService.search(queryInList); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewest)); + + // in range + String queryInRange = String.format("processes: identifier eq '%s' and creationDate in <%s : %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); + processes = searchService.search(queryInRange); + comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + + // sort + String query = String.format("processes: identifier eq '%s' sort by creationDate", net.getIdentifier()); + processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); + + query = String.format("processes: identifier eq '%s' sort by creationDate desc", net.getIdentifier()); + processes = searchService.search(query); + comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(netNewest, netNewer, net)); + } + + @Test + public void testPagination() { + List<PetriNet> nets = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + nets.add(importPetriNet("search/search_test.xml", VersionType.MAJOR)); + } + + String queryOne = String.format("process: identifier eq '%s'", "search_test"); + String queryMore = String.format("processes: identifier eq '%s'", "search_test"); + String queryMoreCustomPagination = String.format("processes: identifier eq '%s' page 1 size 5", "search_test"); + + long count = searchService.count(queryOne); + assert count == 50; + + Object process = searchService.search(queryOne); + comparePetriNets(convertToPetriNet(process), nets.get(0)); + + Object processes = searchService.search(queryMore); + comparePetriNets(convertToPetriNetList(processes), nets.subList(0, 19)); + + processes = searchService.search(queryMoreCustomPagination); + comparePetriNets(convertToPetriNetList(processes), nets.subList(5, 9)); } } diff --git a/src/test/resources/petriNets/search/search_test3.xml b/src/test/resources/petriNets/search/search_test3.xml new file mode 100644 index 00000000000..9621b6295d1 --- /dev/null +++ b/src/test/resources/petriNets/search/search_test3.xml @@ -0,0 +1,207 @@ +<document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="https://petriflow.com/petriflow.schema.xsd"> + <id>search_test3</id> + <initials>ST3</initials> + <title>Search Test 3 + check_circle + true + true + false + + text_immediate + + <init>test</init> + </data> + <data type="number" immediate="true"> + <id>number_immediate</id> + <title/> + <init>54</init> + </data> + <data type="enumeration_map" immediate="true"> + <id>enumeration_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <init>key2</init> + </data> + <data type="multichoice_map" immediate="true"> + <id>multichoice_map_immediate</id> + <title/> + <options> + <option key="key1">value1</option> + <option key="key2">value2</option> + <option key="key3">value3</option> + </options> + <inits> + <init>key1</init> + <init>key2</init> + </inits> + </data> + <data type="boolean" immediate="true"> + <id>boolean_immediate</id> + <title/> + <init>true</init> + </data> + <data type="date" immediate="true"> + <id>date_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDate.now()</init> + </data> + <data type="dateTime" immediate="true"> + <id>date_time_immediate</id> + <title/> + <init dynamic="true">java.time.LocalDateTime.now()</init> + </data> + <transition> + <id>search_test_t1</id> + <x>260</x> + <y>100</y> + <label>Test3</label> + <dataGroup> + <id>search_test_t1_0</id> + <cols>4</cols> + <layout>grid</layout> + <dataRef> + <id>text_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>0</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>number_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>1</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>enumeration_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>2</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>multichoice_map_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>3</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>boolean_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>4</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>5</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + <dataRef> + <id>date_time_immediate</id> + <logic> + <behavior>editable</behavior> + </logic> + <layout> + <x>0</x> + <y>6</y> + <rows>1</rows> + <cols>4</cols> + <template>material</template> + <appearance>outline</appearance> + </layout> + </dataRef> + </dataGroup> + <event type="assign"> + <id>search_test_t1_asign</id> + </event> + <event type="finish"> + <id>search_test_t1_fiish</id> + </event> + <event type="cancel"> + <id>search_test_t1_cacel</id> + </event> + <event type="delegate"> + <id>search_test_t1_deleate</id> + </event> + </transition> + <place> + <id>p1</id> + <x>180</x> + <y>100</y> + <tokens>1</tokens> + <static>false</static> + </place> + <place> + <id>p2</id> + <x>340</x> + <y>100</y> + <tokens>0</tokens> + <static>false</static> + </place> + <arc> + <id>a1</id> + <type>regular</type> + <sourceId>p1</sourceId> + <destinationId>search_test_t1</destinationId> + <multiplicity>1</multiplicity> + </arc> + <arc> + <id>a2</id> + <type>regular</type> + <sourceId>search_test_t1</sourceId> + <destinationId>p2</destinationId> + <multiplicity>1</multiplicity> + </arc> +</document> \ No newline at end of file From 127a4db9cbb4ad8d12afa66e530f72f36a0ca140 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Mon, 10 Mar 2025 23:05:59 +0100 Subject: [PATCH 23/27] [NAE-1997] Query language - add search case tests for pagination, sorting, in list/in range operators --- .../engine/search/QueryLangEvaluator.java | 4 +- .../engine/search/antlr4/QueryLang.g4 | 18 +- .../engine/search/SearchCaseTest.java | 518 ++++++++---------- .../engine/search/utils/SearchTestUtils.java | 63 +++ 4 files changed, 308 insertions(+), 295 deletions(-) create mode 100644 src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 44131af385f..90cd6744a4f 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -1031,12 +1031,12 @@ public void exitDataNumberRange(QueryLangParser.DataNumberRangeContext ctx) { String rightNumberAsString; if (ctx.inRangeNumberComparison().intRange() != null) { leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); } else { leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); } diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index fc6aa609c2e..0aaa54dd0b6 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -143,26 +143,26 @@ identifierComparison: IDENTIFIER SPACE stringComparison # identifierBasic versionComparison: VERSION SPACE (NOT SPACE?)? op=(EQ | LT | GT | LTE | GTE) SPACE VERSION_NUMBER # versionBasic | VERSION SPACE inListVersionComparison # versionListCmp | VERSION SPACE inRangeVersionComparison # versionRangeCmp - ; // todo NAE-1997: in list/in range?? + ; creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic | CREATION_DATE SPACE dateTimeComparison # cdDateTimeBasic | CREATION_DATE SPACE inListDateComparison # cdDateList | CREATION_DATE SPACE inRangeDateComparison # cdDateRange ; -processIdComparison: PROCESS_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +processIdComparison: PROCESS_ID SPACE stringComparison ; processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison ; processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange ; -authorComparison: AUTHOR SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +authorComparison: AUTHOR SPACE stringComparison ; transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic | TRANSITION_ID SPACE inListStringComparison # transitionIdList | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange ; -stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; // todo NAE-1997: in list/in range?? only 2 values -userIdComparison: USER_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison -caseIdComparison: CASE_ID SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; +userIdComparison: USER_ID SPACE stringComparison ; +caseIdComparison: CASE_ID SPACE stringComparison ; lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic | LAST_ASSIGN SPACE inListDateComparison # laDateList @@ -185,7 +185,7 @@ emailComparison: EMAIL SPACE stringComparison # emailBasic | EMAIL SPACE inListStringComparison # emailList | EMAIL SPACE inRangeStringComparison # emailRange ; -dataValueComparison: dataValue SPACE stringComparison # dataString // todo NAE-1997: how to translate to elastic query?? +dataValueComparison: dataValue SPACE stringComparison # dataString | dataValue SPACE numberComparison # dataNumber | dataValue SPACE dateComparison # dataDate | dataValue SPACE dateTimeComparison # dataDatetime @@ -205,8 +205,8 @@ placesComparison: places SPACE numberComparison # placesBasic | places SPACE inListNumberComparison # placesList | places SPACE inRangeNumberComparison # placesRange ; -tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; // todo NAE-1997: in list/in range?? only 2 values -tasksUserIdComparison: tasksUserId SPACE stringComparison ; // todo NAE-1997: in list/in range?? basicly objectIdComparison +tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; +tasksUserIdComparison: tasksUserId SPACE stringComparison ; // basic comparisons objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index 81924faa9ff..293ba2f2a8c 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -12,7 +12,6 @@ import com.netgrif.application.engine.search.utils.SearchUtils; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; -import com.netgrif.application.engine.workflow.service.interfaces.IWorkflowService; import com.netgrif.application.engine.workflow.web.responsebodies.DataSet; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; @@ -25,11 +24,13 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateString; import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; @Slf4j @@ -37,8 +38,6 @@ @ActiveProfiles({"test"}) @ExtendWith(SpringExtension.class) public class SearchCaseTest { - @Autowired - private IWorkflowService workflowService; public static final String TEST_TRANSITION_ID = "search_test_t1"; @@ -65,115 +64,119 @@ private PetriNet importPetriNet(String fileName) { return testNet; } - private IUser createUser(String name, String surname, String email, String authority) { + private IUser createUser(String name, String surname, String email) { User user = new User(email, "password", name, surname); - Authority[] authorities = new Authority[]{auths.get(authority)}; + Authority[] authorities = new Authority[]{auths.get("user")}; ProcessRole[] processRoles = new ProcessRole[]{}; return importHelper.createUser(user, authorities, processRoles); } - private static Case convertToCase(Object caseObject) { - assert caseObject instanceof Case; - return (Case) caseObject; - } - - private static List<Case> convertToCaseList(Object caseListObject) { - assert caseListObject instanceof List<?>; - for (Object caseObject : (List<?>) caseListObject) { - assert caseObject instanceof Case; - } + private void searchAndCompare(String query, Case expectedResult) { + long count = searchService.count(query); + assert count == 1; - return (List<Case>) caseListObject; + Object actual = searchService.search(query); + compareById(convertToObject(actual, Case.class), expectedResult, Case::getStringId); } - private void compareCases(Case actual, Case expected) { - assert actual.getStringId().equals(expected.getStringId()); + private void searchAndCompare(String query, List<Case> expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, Case.class), expected, Case::getStringId); } - private void compareCases(Case actual, List<Case> expected) { - List<String> expectedStringIds = expected.stream().map(Case::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsList(String query, List<Case> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert expectedStringIds.contains(actual.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, Case.class), expected, Case::getStringId); } - private void compareCases(List<Case> actual, List<Case> expected) { - List<String> actualStringIds = actual.stream().map(Case::getStringId).collect(Collectors.toList()); - List<String> expectedStringIds = expected.stream().map(Case::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsListInOrder(String query, List<Case> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert actualStringIds.containsAll(expectedStringIds); + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, Case.class), expected, Case::getStringId); } @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml"); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net); - Case caze = importHelper.createCase("Search Test", net); + String query = String.format("case: id eq '%s'", case1.getStringId()); - String query = String.format("case: id eq '%s'", caze.getStringId()); + searchAndCompare(query, case1); - long count = searchService.count(query); - assert count == 1; + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by id", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by id desc", net.getIdentifier()); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), caze); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case2, case1)); } @Test public void testSearchByProcessId() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net2); - String query = String.format("case: processId eq '%s'", net.getStringId()); + String queryEq = String.format("case: processId eq '%s'", net.getStringId()); String queryOther = String.format("case: processId eq '%s'", net2.getStringId()); String queryMore = String.format("cases: processId eq '%s'", net.getStringId()); - long count = searchService.count(query); - assert count == 2; - - count = searchService.count(queryOther); - assert count == 1; + searchAndCompare(queryEq, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // sort + String querySort = String.format("cases: processIdentifier in ('%s', '%s') sort by processId", net.getIdentifier(), net2.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier in ('%s', '%s') sort by processId desc", net.getIdentifier(), net2.getIdentifier()); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); - - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); } @Test public void testSearchByProcessIdentifier() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); - + PetriNet net3 = importPetriNet("search/search_test3.xml"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net2); + Case case4 = importHelper.createCase("Search Test3", net3); String query = String.format("case: processIdentifier eq '%s'", net.getIdentifier()); String queryOther = String.format("case: processIdentifier eq '%s'", net2.getIdentifier()); String queryMore = String.format("cases: processIdentifier eq '%s'", net.getIdentifier()); - long count = searchService.count(query); - assert count == 2; + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - count = searchService.count(queryOther); - assert count == 1; + // in list + String queryInList = String.format("cases: processIdentifier in ('%s', '%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2, case3, case4)); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // in range + String queryInRange = String.format("cases: processIdentifier in <'%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case1, case2, case3)); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); + // sort + String querySort = String.format("cases: processIdentifier in ('%s', '%s') sort by processIdentifier", net.getIdentifier(), net2.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier in ('%s', '%s') sort by processIdentifier desc", net.getIdentifier(), net2.getIdentifier()); - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); } @Test @@ -187,26 +190,29 @@ public void testSearchByTitle() { String queryOther = String.format("case: title eq '%s'", case3.getTitle()); String queryMore = String.format("cases: title eq '%s'", case1.getTitle()); - long count = searchService.count(query); - assert count == 2; + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - count = searchService.count(queryOther); - assert count == 1; + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and title in ('%s', '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and title in <'%s' : '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by title", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by title desc", net.getIdentifier()); - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); } @Test public void testSearchByCreationDate() { PetriNet net = importPetriNet("search/search_test.xml"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test", net); @@ -217,44 +223,33 @@ public void testSearchByCreationDate() { String queryGt = String.format("cases: processIdentifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); String queryGte = String.format("cases: processIdentifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(case1.getCreationDate())); - long count = searchService.count(queryEq); - assert count == 1; - - Object foundCase = searchService.search(queryEq); - compareCases(convertToCase(foundCase), case1); - - count = searchService.count(queryLt); - assert count == 2; + searchAndCompare(queryEq, case1); + searchAndCompareAsList(queryLt, List.of(case1, case2)); + searchAndCompareAsList(queryLte, List.of(case1, case2, case3)); + searchAndCompareAsList(queryGt, List.of(case2, case3)); + searchAndCompareAsList(queryGte, List.of(case1, case2, case3)); - Object cases = searchService.search(queryLt); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and creationDate in (%s, %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); + searchAndCompareAsList(queryInList, List.of(case1, case3)); - count = searchService.count(queryLte); - assert count == 3; + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and creationDate in <%s : %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); - cases = searchService.search(queryLte); - compareCases(convertToCaseList(cases), List.of(case1, case2, case3)); + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by creationDate", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by creationDate desc", net.getIdentifier()); - count = searchService.count(queryGt); - assert count == 2; - - cases = searchService.search(queryGt); - compareCases(convertToCaseList(cases), List.of(case2, case3)); - - count = searchService.count(queryGte); - assert count == 3; - - cases = searchService.search(queryGte); - compareCases(convertToCaseList(cases), List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case2, case1)); } @Test public void testSearchByAuthor() { PetriNet net = importPetriNet("search/search_test.xml"); - - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); - + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); Case case1 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); Case case2 = importHelper.createCase("Search Test", net, user1.transformToLoggedUser()); Case case3 = importHelper.createCase("Search Test2", net, user2.transformToLoggedUser()); @@ -263,31 +258,25 @@ public void testSearchByAuthor() { String queryOther = String.format("case: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user2.getStringId()); String queryMore = String.format("cases: processIdentifier eq '%s' and author eq '%s'", net.getIdentifier(), user1.getStringId()); - long count = searchService.count(query); - assert count == 2; - - count = searchService.count(queryOther); - assert count == 1; - - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by author", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by author desc", net.getIdentifier()); - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); + searchAndCompareAsListInOrder(querySort2, List.of(case3, case1, case2)); } @Test public void testSearchByPlaces() throws InterruptedException { PetriNet net = importPetriNet("search/search_test.xml"); - - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - - Case case1 = importHelper.createCase("Search Test", net); - Case case2 = importHelper.createCase("Search Test", net); - Case case3 = importHelper.createCase("Search Test2", net); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + Case case1 = importHelper.createCase("Search Test1", net); + Case case2 = importHelper.createCase("Search Test2", net); + Case case3 = importHelper.createCase("Search Test3", net); importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); @@ -300,28 +289,30 @@ public void testSearchByPlaces() throws InterruptedException { Thread.sleep(3000); - long count = searchService.count(query); - assert count == 2; + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - count = searchService.count(queryOther); - assert count == 1; + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and places.p1.marking in (1)", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case3)); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and places.p1.marking in <0 : 1>", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case3)); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking", net.getIdentifier()); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking desc", net.getIdentifier()); - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); } @Test - public void testSearchByTaskState() { // todo NAE-1997: not indexing tasks.state + public void testSearchByTaskState() throws InterruptedException { // todo NAE-1997: not indexing tasks.state PetriNet net = importPetriNet("search/search_test.xml"); - - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - + IUser user1 = createUser("Name1", "Surname1", "Email1"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); @@ -335,29 +326,25 @@ public void testSearchByTaskState() { // todo NAE-1997: not indexing tasks.state String queryOther = String.format("case: tasks.%s.state eq %s", TEST_TRANSITION_ID, "enabled"); String queryMore = String.format("cases: tasks.%s.state eq %s", TEST_TRANSITION_ID, "disabled"); - long count = searchService.count(query); - assert count == 2; - - count = searchService.count(queryOther); - assert count == 1; + Thread.sleep(3000); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.state", net.getIdentifier(), TEST_TRANSITION_ID); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.state desc", net.getIdentifier(), TEST_TRANSITION_ID); - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); } @Test public void testSearchByTaskUserId() { // todo NAE-1997: not indexing tasks.userId PetriNet net = importPetriNet("search/search_test.xml"); - - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); - + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); @@ -370,27 +357,23 @@ public void testSearchByTaskUserId() { // todo NAE-1997: not indexing tasks.user String queryOther = String.format("case: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user2.getStringId()); String queryMore = String.format("cases: tasks.%s.userId eq '%s'", TEST_TRANSITION_ID, user1.getStringId()); - long count = searchService.count(query); - assert count == 2; + searchAndCompare(query, List.of(case1, case2)); + searchAndCompare(queryOther, case3); + searchAndCompareAsList(queryMore, List.of(case1, case2)); - count = searchService.count(queryOther); - assert count == 1; + // sort + String querySort = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId", net.getIdentifier(), TEST_TRANSITION_ID); + String querySort2 = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId desc", net.getIdentifier(), TEST_TRANSITION_ID); - Object foundCase = searchService.search(query); - compareCases(convertToCase(foundCase), List.of(case1, case2)); - - foundCase = searchService.search(queryOther); - compareCases(convertToCase(foundCase), case3); - - Object cases = searchService.search(queryMore); - compareCases(convertToCaseList(cases), List.of(case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); } @Test public void testSearchByDataValue() throws InterruptedException { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test2", net); @@ -423,7 +406,7 @@ public void testSearchByDataValue() throws InterruptedException { "date_immediate", dateField, "date_time_immediate", dateTimeField ))); - importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); + case2 = importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()).getCase(); String queryTextEq = String.format("case: data.text_immediate.value eq %s", "'test'"); String queryTextContains = String.format("case: data.text_immediate.value contains %s", "'es'"); @@ -450,159 +433,126 @@ public void testSearchByDataValue() throws InterruptedException { Thread.sleep(3000); - long count = searchService.count(queryTextEq); - assert count == 1; - - count = searchService.count(queryTextContains); - assert count == 1; - - count = searchService.count(queryBoolean); - assert count == 1; - + // text + searchAndCompare(queryTextEq, case1); + searchAndCompare(queryTextContains, case1); + + // boolean + searchAndCompare(queryBoolean, case1); + + // number + searchAndCompare(queryNumberEq, case1); + searchAndCompare(queryNumberLt, List.of(case1, case2)); + searchAndCompare(queryNumberLte, List.of(case1, case2)); + searchAndCompare(queryNumberGt, case1); + searchAndCompare(queryNumberGte, case1); + + // date + searchAndCompare(queryDateEq, case1); + searchAndCompare(queryDateLt, List.of(case1, case2)); + searchAndCompare(queryDateLte, List.of(case1, case2)); + searchAndCompare(queryDateGt, case1); + searchAndCompare(queryDateGte, case1); + + // datetime + searchAndCompare(queryDateTimeEq, case1); + searchAndCompare(queryDateTimeLt, List.of(case1, case2)); + searchAndCompare(queryDateTimeLte, List.of(case1, case2)); + searchAndCompare(queryDateTimeGt, case1); + searchAndCompare(queryDateTimeGte, case1); + + // enumeration/multichoice // todo NAE-1997: should use keyValue, textValue represents only value -// count = searchService.count(queryEnumerationEq); -// assert count == 1; -// -// count = searchService.count(queryEnumerationContains); -// assert count == 1; -// -// count = searchService.count(queryMultichoiceEq); -// assert count == 1; +// searchAndCompare(queryEnumerationEq, case1); +// searchAndCompare(queryEnumerationContains, case1); // -// count = searchService.count(queryMultichoiceContains); -// assert count == 1; - - count = searchService.count(queryNumberEq); - assert count == 1; - - count = searchService.count(queryNumberLt); - assert count == 2; - - count = searchService.count(queryNumberLte); - assert count == 2; - - count = searchService.count(queryNumberGt); - assert count == 1; - - count = searchService.count(queryNumberGte); - assert count == 1; - - count = searchService.count(queryDateEq); - assert count == 1; - - count = searchService.count(queryDateLt); - assert count == 2; - - count = searchService.count(queryDateLte); - assert count == 2; - - count = searchService.count(queryDateGt); - assert count == 1; - - count = searchService.count(queryDateGte); - assert count == 1; +// searchAndCompare(queryMultichoiceEq, case1); +// searchAndCompare(queryMultichoiceContains, case1); - count = searchService.count(queryDateTimeEq); - assert count == 1; - - count = searchService.count(queryDateTimeLt); - assert count == 2; + // in list text + String queryInList = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in ('test', 'other')", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2)); - count = searchService.count(queryDateTimeLte); - assert count == 2; + // in range text + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in <'other' : 'test')", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case2)); - count = searchService.count(queryDateTimeGt); - assert count == 1; + // in list number + queryInList = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in (2, 54)", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1, case2)); - count = searchService.count(queryDateTimeGte); - assert count == 1; + // in range number + queryInRange = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in <2 : 54)", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case2)); - Object foundCase = searchService.search(queryTextEq); - compareCases(convertToCase(foundCase), case1); + // in list date + queryInList = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in (%s, %s)", net.getIdentifier(), toDateString(LocalDateTime.now()), toDateString(LocalDateTime.now().minusDays(5))); + searchAndCompareAsList(queryInList, List.of(case1, case2)); - foundCase = searchService.search(queryTextContains); - compareCases(convertToCase(foundCase), case1); - - foundCase = searchService.search(queryBoolean); - compareCases(convertToCase(foundCase), case1); - -// foundCase = searchService.search(queryEnumerationEq); -// compareCases(convertToCase(foundCase), case1); -// -// foundCase = searchService.search(queryEnumerationContains); -// compareCases(convertToCase(foundCase), case1); -// -// foundCase = searchService.search(queryMultichoiceEq); -// compareCases(convertToCase(foundCase), case1); -// -// foundCase = searchService.search(queryMultichoiceContains); -// compareCases(convertToCase(foundCase), case1); + // in range date + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in <%s : %s)", net.getIdentifier(), toDateString(LocalDateTime.now().minusDays(10)), toDateString(LocalDateTime.now().plusDays(1))); + searchAndCompareAsList(queryInRange, List.of(case1, case2)); - foundCase = searchService.search(queryNumberEq); - compareCases(convertToCase(foundCase), case1); + // in list datetime + LocalDateTime localDateTime1 = (LocalDateTime) case1.getDataSet().get("date_time_immediate").getRawValue(); + LocalDateTime localDateTime2 = (LocalDateTime) case2.getDataSet().get("date_time_immediate").getRawValue(); + queryInList = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in (%s, %s)", net.getIdentifier(), toDateTimeString(localDateTime1), toDateTimeString(localDateTime2)); + searchAndCompareAsList(queryInList, List.of(case1, case2)); - foundCase = searchService.search(queryNumberLt); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // in range datetime + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in <%s : %s)", net.getIdentifier(), toDateTimeString(localDateTime2), toDateTimeString(localDateTime1)); + searchAndCompareAsList(queryInRange, List.of(case2)); - foundCase = searchService.search(queryNumberLte); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // todo NAE-1997: sort by data - indexation change needed + } - foundCase = searchService.search(queryNumberGt); - compareCases(convertToCase(foundCase), case1); + @Test + public void testSearchByDataOptions() throws InterruptedException { + PetriNet net = importPetriNet("search/search_test.xml"); - foundCase = searchService.search(queryNumberGte); - compareCases(convertToCase(foundCase), case1); + Case case1 = importHelper.createCase("Search Test", net); -// foundCase = searchService.search(queryDateEq); -// compareCases(convertToCase(foundCase), case1); -// -// foundCase = searchService.search(queryDateLt); -// compareCases(convertToCase(foundCase), List.of(case1, case2)); -// -// foundCase = searchService.search(queryDateLte); -// compareCases(convertToCase(foundCase), List.of(case1, case2)); -// -// foundCase = searchService.search(queryDateGt); -// compareCases(convertToCase(foundCase), case1); -// -// foundCase = searchService.search(queryDateGte); -// compareCases(convertToCase(foundCase), case1); + String queryEq = String.format("case: data.enumeration_map_immediate.options eq '%s'", "key1"); + String queryContains = String.format("case: data.enumeration_map_immediate.options contains '%s'", "key1"); - foundCase = searchService.search(queryDateTimeEq); - compareCases(convertToCase(foundCase), case1); + Thread.sleep(3000); - foundCase = searchService.search(queryDateTimeLt); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + searchAndCompare(queryEq, case1); + searchAndCompare(queryContains, case1); - foundCase = searchService.search(queryDateTimeLte); - compareCases(convertToCase(foundCase), List.of(case1, case2)); + // in list + String queryInList = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in ('key1')", net.getIdentifier()); + searchAndCompareAsList(queryInList, List.of(case1)); - foundCase = searchService.search(queryDateTimeGt); - compareCases(convertToCase(foundCase), case1); + // in range + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in <'key1' : 'key2')", net.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(case1)); - foundCase = searchService.search(queryDateTimeGte); - compareCases(convertToCase(foundCase), case1); + // todo NAE-1997: sort by data - indexation change needed } @Test - public void testSearchByDataOptions() { + public void testPagination() { PetriNet net = importPetriNet("search/search_test.xml"); + List<Case> cases = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + cases.add(importHelper.createCase("Search Test", net)); + } - Case case1 = importHelper.createCase("Search Test", net); - - String queryEq = String.format("case: data.enumeration_map_immediate.options eq '%s'", "key1"); - String queryContains = String.format("case: data.enumeration_map_immediate.options contains '%s'", "key1"); + String queryOne = String.format("case: processIdentifier eq '%s'", "search_test"); + String queryMore = String.format("cases: processIdentifier eq '%s'", "search_test"); + String queryMoreCustomPagination = String.format("cases: processIdentifier eq '%s' page 1 size 5", "search_test"); - long count = searchService.count(queryEq); - assert count == 1; + long count = searchService.count(queryOne); + assert count == 50; - count = searchService.count(queryContains); - assert count == 1; + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, Case.class), cases.get(0), Case::getStringId); - Object foundCase = searchService.search(queryEq); - compareCases(convertToCase(foundCase), case1); + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, Case.class), cases.subList(0, 19), Case::getStringId); - foundCase = searchService.search(queryContains); - compareCases(convertToCase(foundCase), case1); + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, Case.class), cases.subList(5, 9), Case::getStringId); } } diff --git a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java new file mode 100644 index 00000000000..f1ee5e9a658 --- /dev/null +++ b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java @@ -0,0 +1,63 @@ +package com.netgrif.application.engine.search.utils; + +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class SearchTestUtils { + + public static <T> T convertToObject(Object object, Class<T> targetClass) { + assert targetClass.isInstance(object); + return targetClass.cast(object); + } + + public static <T> List<T> convertToObjectList(Object objectList, Class<T> targetClass) { + assert objectList instanceof List<?>; + for (Object object : (List<?>) objectList) { + assert targetClass.isInstance(object); + } + + return (List<T>) objectList; + } + + public static <T> void compareById(T actual, T expected, Function<T, String> getId) { + assert getId.apply(actual).equals(getId.apply(expected)); + } + + public static <T> void compareById(T actual, List<T> expected, Function<T, String> getId) { + List<String> expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert expectedIds.contains(getId.apply(actual)); + } + + public static <T> void compareById(List<T> actual, List<T> expected, Function<T, String> getId) { + List<String> actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List<String> expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert actualIds.containsAll(expectedIds); + } + + public static <T> void compareByIdInOrder(List<T> actual, List<T> expected, Function<T, String> getId) { + List<String> actualIds = actual.stream() + .map(getId) + .collect(Collectors.toList()); + List<String> expectedIds = expected.stream() + .map(getId) + .collect(Collectors.toList()); + + assert actualIds.containsAll(expectedIds); + + int lastIndex = -1; + for (String expectedId : expectedIds) { + int currentIndex = actualIds.indexOf(expectedId); + assert currentIndex > lastIndex; + lastIndex = currentIndex; + } + } +} From cee6e36bb8e9eb681b989a99d32d74ac34068b7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Tue, 11 Mar 2025 00:51:00 +0100 Subject: [PATCH 24/27] [NAE-1997] Query language - update grammar in range (replace closed interval with []) + update tests - update tests for task search --- .../engine/search/antlr4/QueryLang.g4 | 12 +- .../engine/search/QueryLangTest.java | 16 +- .../engine/search/SearchCaseTest.java | 22 +- .../engine/search/SearchTaskTest.java | 266 +++++------------- .../engine/search/utils/SearchTestUtils.java | 2 +- 5 files changed, 104 insertions(+), 214 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 0aaa54dd0b6..5329a2dd5c2 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -298,12 +298,12 @@ doubleList: '(' SPACE? (DOUBLE SPACE? (',' SPACE? DOUBLE SPACE? )* )? SPACE? ')' dateList: '(' SPACE? (DATE SPACE? (',' SPACE? DATE SPACE? )* )? SPACE? ')' ; dateTimeList: '(' SPACE? (DATETIME SPACE? (',' SPACE? DATETIME SPACE? )* )? SPACE? ')' ; versionList: '(' SPACE? (VERSION_NUMBER SPACE? (',' SPACE? VERSION_NUMBER SPACE? )* )? SPACE? ')' ; -stringRange: leftEndpoint=('(' | '<') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | '>') ; -intRange: leftEndpoint=('(' | '<') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | '>') ; -doubleRange: leftEndpoint=('(' | '<') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | '>') ; -dateRange: leftEndpoint=('(' | '<') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | '>') ; -dateTimeRange: leftEndpoint=('(' | '<') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | '>') ; -versionRange: leftEndpoint=('(' | '<') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | '>') ; +stringRange: leftEndpoint=('(' | '[') SPACE? STRING SPACE? ':' SPACE? STRING SPACE? rightEndpoint=(')' | ']') ; +intRange: leftEndpoint=('(' | '[') SPACE? INT SPACE? ':' SPACE? INT SPACE? rightEndpoint=(')' | ']') ; +doubleRange: leftEndpoint=('(' | '[') SPACE? DOUBLE SPACE? ':' SPACE? DOUBLE SPACE? rightEndpoint=(')' | ']') ; +dateRange: leftEndpoint=('(' | '[') SPACE? DATE SPACE? ':' SPACE? DATE SPACE? rightEndpoint=(')' | ']') ; +dateTimeRange: leftEndpoint=('(' | '[') SPACE? DATETIME SPACE? ':' SPACE? DATETIME SPACE? rightEndpoint=(')' | ']') ; +versionRange: leftEndpoint=('(' | '[') SPACE? VERSION_NUMBER SPACE? ':' SPACE? VERSION_NUMBER SPACE? rightEndpoint=(')' | ']') ; STRING: '\'' (~('\'' | '\r' | '\n'))* '\'' ; // todo NAE-1997: escape??? INT: DIGIT+ ; DOUBLE: DIGIT+ '.' DIGIT+ ; diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 3db8b9ba716..6d30218fcca 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -113,7 +113,7 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: version in <1.1.1 : 2.2.2>").getFullMongoQuery(); + actual = evaluateQuery("process: version in [1.1.1 : 2.2.2]").getFullMongoQuery(); builder = new BooleanBuilder(); builder.and(QPetriNet.petriNet.version.major.goe(1) .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.goe(1))) @@ -125,7 +125,7 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery("process: version not in (1.1.1 : 2.2.2>").getFullMongoQuery(); + actual = evaluateQuery("process: version not in (1.1.1 : 2.2.2]").getFullMongoQuery(); builder = new BooleanBuilder(); builder.and(QPetriNet.petriNet.version.major.gt(1) .or(QPetriNet.petriNet.version.major.eq(1L).and(QPetriNet.petriNet.version.minor.gt(1))) @@ -1590,12 +1590,12 @@ private static void checkStringComparison(MongoDbUtils<?> mongoDbUtils, String r compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s in <'test1' : 'test2'>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s in ['test1' : 'test2']", resource, attribute)).getFullMongoQuery(); expected = stringPath.goe("test1").and(stringPath.loe("test2")); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2'>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullMongoQuery(); expected = stringPath.gt("test1").and(stringPath.loe("test2")).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -1649,12 +1649,12 @@ private static void checkDateComparison(MongoDbUtils<?> mongoDbUtils, String res compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s in <2011-12-03T10:15:30 : 2011-12-03T11:15:30>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); expected = dateTimePath.goe(date1).and(dateTimePath.loe(date2)); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullMongoQuery(); expected = dateTimePath.gt(date1).and(dateTimePath.loe(date2)).not(); compareMongoQueries(mongoDbUtils, actual, expected); @@ -1674,12 +1674,12 @@ private static void checkDateComparison(MongoDbUtils<?> mongoDbUtils, String res compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s in <2011-12-03 : 2011-12-03>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); expected = dateTimePath.goe(date4).and(dateTimePath.loe(date5)); compareMongoQueries(mongoDbUtils, actual, expected); - actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03>", resource, attribute)).getFullMongoQuery(); + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullMongoQuery(); expected = dateTimePath.gt(date4).and(dateTimePath.loe(date5)).not(); compareMongoQueries(mongoDbUtils, actual, expected); diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index 293ba2f2a8c..58f07a50818 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -100,7 +100,7 @@ private void searchAndCompareAsListInOrder(String query, List<Case> expected) { assert count == expected.size(); Object actual = searchService.search(query); - compareById(convertToObjectList(actual, Case.class), expected, Case::getStringId); + compareByIdInOrder(convertToObjectList(actual, Case.class), expected, Case::getStringId); } @Test @@ -168,7 +168,7 @@ public void testSearchByProcessIdentifier() { searchAndCompareAsList(queryInList, List.of(case1, case2, case3, case4)); // in range - String queryInRange = String.format("cases: processIdentifier in <'%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + String queryInRange = String.format("cases: processIdentifier in ['%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); searchAndCompareAsList(queryInRange, List.of(case1, case2, case3)); // sort @@ -199,7 +199,7 @@ public void testSearchByTitle() { searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); // in range - String queryInRange = String.format("cases: processIdentifier eq '%s' and title in <'%s' : '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); + String queryInRange = String.format("cases: processIdentifier eq '%s' and title in ['%s' : '%s')", net.getIdentifier(), case1.getTitle(), case3.getTitle()); searchAndCompareAsList(queryInRange, List.of(case1, case2)); // sort @@ -234,7 +234,7 @@ public void testSearchByCreationDate() { searchAndCompareAsList(queryInList, List.of(case1, case3)); // in range - String queryInRange = String.format("cases: processIdentifier eq '%s' and creationDate in <%s : %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); + String queryInRange = String.format("cases: processIdentifier eq '%s' and creationDate in [%s : %s)", net.getIdentifier(), toDateTimeString(case1.getCreationDate()), toDateTimeString(case3.getCreationDate())); searchAndCompareAsList(queryInRange, List.of(case1, case2)); // sort @@ -298,14 +298,14 @@ public void testSearchByPlaces() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case3)); // in range - String queryInRange = String.format("cases: processIdentifier eq '%s' and places.p1.marking in <0 : 1>", net.getIdentifier()); + String queryInRange = String.format("cases: processIdentifier eq '%s' and places.p1.marking in [1 : 100]", net.getIdentifier()); searchAndCompareAsList(queryInRange, List.of(case3)); // sort String querySort = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking", net.getIdentifier()); String querySort2 = String.format("cases: processIdentifier eq '%s' sort by places.p2.marking desc", net.getIdentifier()); - searchAndCompareAsListInOrder(querySort, List.of(case3, case1, case2)); + searchAndCompareAsListInOrder(querySort, List.of(case1, case2, case3)); searchAndCompareAsListInOrder(querySort2, List.of(case1, case2, case3)); } @@ -474,7 +474,7 @@ public void testSearchByDataValue() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case1, case2)); // in range text - String queryInRange = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in <'other' : 'test')", net.getIdentifier()); + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.text_immediate.value in ['other' : 'test')", net.getIdentifier()); searchAndCompareAsList(queryInRange, List.of(case2)); // in list number @@ -482,7 +482,7 @@ public void testSearchByDataValue() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case1, case2)); // in range number - queryInRange = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in <2 : 54)", net.getIdentifier()); + queryInRange = String.format("cases: processIdentifier eq '%s' and data.number_immediate.value in [2 : 54)", net.getIdentifier()); searchAndCompareAsList(queryInRange, List.of(case2)); // in list date @@ -490,7 +490,7 @@ public void testSearchByDataValue() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case1, case2)); // in range date - queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in <%s : %s)", net.getIdentifier(), toDateString(LocalDateTime.now().minusDays(10)), toDateString(LocalDateTime.now().plusDays(1))); + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_immediate.value in [%s : %s)", net.getIdentifier(), toDateString(LocalDateTime.now().minusDays(10)), toDateString(LocalDateTime.now().plusDays(1))); searchAndCompareAsList(queryInRange, List.of(case1, case2)); // in list datetime @@ -500,7 +500,7 @@ public void testSearchByDataValue() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case1, case2)); // in range datetime - queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in <%s : %s)", net.getIdentifier(), toDateTimeString(localDateTime2), toDateTimeString(localDateTime1)); + queryInRange = String.format("cases: processIdentifier eq '%s' and data.date_time_immediate.value in [%s : %s)", net.getIdentifier(), toDateTimeString(localDateTime2), toDateTimeString(localDateTime1)); searchAndCompareAsList(queryInRange, List.of(case2)); // todo NAE-1997: sort by data - indexation change needed @@ -525,7 +525,7 @@ public void testSearchByDataOptions() throws InterruptedException { searchAndCompareAsList(queryInList, List.of(case1)); // in range - String queryInRange = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in <'key1' : 'key2')", net.getIdentifier()); + String queryInRange = String.format("cases: processIdentifier eq '%s' and data.enumeration_map_immediate.options in ['key1' : 'key2')", net.getIdentifier()); searchAndCompareAsList(queryInRange, List.of(case1)); // todo NAE-1997: sort by data - indexation change needed diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index bf9db10f81c..89c934a3b98 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -10,6 +10,7 @@ import com.netgrif.application.engine.search.utils.SearchUtils; import com.netgrif.application.engine.startup.ImportHelper; import com.netgrif.application.engine.workflow.domain.Case; +import com.netgrif.application.engine.workflow.domain.State; import com.netgrif.application.engine.workflow.domain.Task; import com.netgrif.application.engine.workflow.service.interfaces.ITaskService; import lombok.extern.slf4j.Slf4j; @@ -22,10 +23,13 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; + @Slf4j @SpringBootTest @ActiveProfiles({"test"}) @@ -67,81 +71,66 @@ private IUser createUser(String name, String surname, String email, String autho return importHelper.createUser(user, authorities, processRoles); } + private void searchAndCompare(String query, Task expectedResult) { + long count = searchService.count(query); + assert count == 1; - private static Task convertToTask(Object taskObject) { - assert taskObject instanceof Task; - return (Task) taskObject; + Object actual = searchService.search(query); + compareById(convertToObject(actual, Task.class), expectedResult, Task::getStringId); } - private static List<Task> convertToTaskList(Object taskListObject) { - assert taskListObject instanceof List<?>; - for (Object taskObject : (List<?>) taskListObject) { - assert taskObject instanceof Task; - } - - return (List<Task>) taskListObject; - } + private void searchAndCompare(String query, List<Task> expected) { + long count = searchService.count(query); + assert count == expected.size(); - private void compareTasks(Task actual, Task expected) { - assert actual.getStringId().equals(expected.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObject(actual, Task.class), expected, Task::getStringId); } - private void compareTasks(Task actual, List<Task> expected) { - List<String> expectedStringIds = expected.stream().map(Task::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsList(String query, List<Task> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert expectedStringIds.contains(actual.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, Task.class), expected, Task::getStringId); } - private void compareTasks(List<Task> actual, List<Task> expected) { - List<String> actualStringIds = actual.stream().map(Task::getStringId).collect(Collectors.toList()); - List<String> expectedStringIds = expected.stream().map(Task::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsListInOrder(String query, List<Task> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert actualStringIds.containsAll(expectedStringIds); + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, Task.class), expected, Task::getStringId); } @Test public void testSearchById() { PetriNet net = importPetriNet("search/search_test.xml"); - Case caze = importHelper.createCase("Search Test", net); - String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String query = String.format("task: id eq '%s'", taskId); - long count = searchService.count(query); - assert count == 1; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), task); + searchAndCompare(query, task); } @Test public void testSearchByTransitionId() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); - Case caze = importHelper.createCase("Search Test", net); Case caze2 = importHelper.createCase("Search Test2", net2); - String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); - String task2Id = caze2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String query = String.format("task: transitionId eq '%s'", TEST_TRANSITION_ID); String queryMore = String.format("tasks: transitionId eq '%s'", TEST_TRANSITION_ID); - long count = searchService.count(query); - assert count == 2; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2)); - - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); } @Test @@ -150,7 +139,6 @@ public void testSearchByTitle() { Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); @@ -158,85 +146,53 @@ public void testSearchByTitle() { String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task3 = taskService.findOne(task3Id); - String query = String.format("task: title eq '%s'", task.getTitle()); - String queryMore = String.format("tasks: title eq '%s'", task.getTitle()); - - long count = searchService.count(query); - assert count == 3; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2, task3)); + String query = String.format("task: title eq '%s'", task.getTitle().getDefaultValue()); + String queryMore = String.format("tasks: title eq '%s'", task.getTitle().getDefaultValue()); - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, List.of(task, task2, task3)); + searchAndCompareAsList(queryMore, List.of(task, task2, task3)); } @Test public void testSearchByState() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - Case case1 = importHelper.createCase("Search Test", net); - Case case2 = importHelper.createCase("Search Test", net); - Case case3 = importHelper.createCase("Search Test2", net); - importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); - importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); - importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task = taskService.findOne(taskId); - String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task2 = taskService.findOne(task2Id); - String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task3 = taskService.findOne(task3Id); - String task4Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); - Task task4 = taskService.findOne(task4Id); - String task5Id = case2.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); - Task task5 = taskService.findOne(task5Id); - String task6Id = case3.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); - Task task6 = taskService.findOne(task6Id); + case1 = importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()).getCase(); + List<Task> case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List<Task> disabled = case1Tasks.stream() + .filter(task -> task.getState().equals(State.DISABLED)) + .collect(Collectors.toList()); + List<Task> enabled = case1Tasks.stream() + .filter(task -> task.getState().equals(State.ENABLED)) + .collect(Collectors.toList()); String query = String.format("task: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); String queryOther = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "enabled"); String queryMore = String.format("tasks: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); - long count = searchService.count(query); - assert count == 4; - - count = searchService.count(queryOther); - assert count == 5; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2, task4, task5)); - - Object foundTasks = searchService.search(queryOther); - compareTasks(convertToTaskList(foundTasks), List.of(task3, task6)); - - foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2, task4, task5)); + searchAndCompare(query, disabled); + searchAndCompareAsList(queryOther, enabled); + searchAndCompareAsList(queryMore, disabled); } @Test public void testSearchByUserId() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task3 = taskService.findOne(task3Id); - importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case3.getStringId(), user2.transformToLoggedUser()); @@ -245,169 +201,103 @@ public void testSearchByUserId() { String queryOther = String.format("task: userId eq '%s'", user2.getStringId()); String queryMore = String.format("tasks: userId eq '%s'", user1.getStringId()); - long count = searchService.count(query); - assert count == 2; - - count = searchService.count(queryOther); - assert count == 1; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2)); - - foundTask = searchService.search(queryOther); - compareTasks(convertToTask(foundTask), task3); - - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, List.of(task, task2)); + searchAndCompare(queryOther, List.of(task3)); + searchAndCompareAsList(queryMore, List.of(task, task2)); } @Test public void testSearchByCaseId() { PetriNet net = importPetriNet("search/search_test.xml"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); - - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task = taskService.findOne(taskId); - String task2Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); - Task task2 = taskService.findOne(task2Id); - String task3Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task3 = taskService.findOne(task3Id); - String task4Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task4 = taskService.findOne(task4Id); + List<Task> case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List<Task> case2Tasks = case2.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); String query = String.format("task: caseId eq '%s'", case1.getStringId()); String queryOther = String.format("task: caseId eq '%s'", case2.getStringId()); String queryMore = String.format("tasks: caseId eq '%s'", case1.getStringId()); - long count = searchService.count(query); - assert count == 3; - - count = searchService.count(queryOther); - assert count == 3; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2)); - - foundTask = searchService.search(queryOther); - compareTasks(convertToTask(foundTask), List.of(task3, task4)); - - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, case1Tasks); + searchAndCompare(queryOther, case2Tasks); + searchAndCompareAsList(queryMore, case1Tasks); } @Test public void testSearchByProcessId() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net2); - - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task = taskService.findOne(taskId); - String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task2 = taskService.findOne(task2Id); - String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); - Task task3 = taskService.findOne(task3Id); + List<Task> netTasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + netTasks.addAll(case2.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList())); + List<Task> net2Tasks = case3.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); String query = String.format("task: processId eq '%s'", net.getStringId()); String queryOther = String.format("task: processId eq '%s'", net2.getStringId()); String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); - long count = searchService.count(query); - assert count == 6; - - count = searchService.count(queryOther); - assert count == 2; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), List.of(task, task2)); - - foundTask = searchService.search(queryOther); - compareTasks(convertToTask(foundTask), task3); - - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, netTasks); + searchAndCompare(queryOther, net2Tasks); + searchAndCompareAsList(queryMore, netTasks); } @Test public void testSearchByLastAssign() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); - LocalDateTime before = LocalDateTime.now(); importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String query = String.format("task: lastAssign eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); - String queryBefore = String.format("task: lastAssign > %s", SearchUtils.toDateTimeString(before)); - String queryMore = String.format("tasks: lastAssign > %s", SearchUtils.toDateTimeString(before)); - - long count = searchService.count(query); - assert count == 1; - - count = searchService.count(queryBefore); - assert count == 2; + String queryBefore = String.format("task: lastAssign gt %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastAssign gt %s", SearchUtils.toDateTimeString(before)); - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), task); - - foundTask = searchService.search(queryBefore); - compareTasks(convertToTask(foundTask), List.of(task, task2)); - - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, task); + searchAndCompare(queryBefore, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); } @Test public void testSearchByLastFinish() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); - LocalDateTime before = LocalDateTime.now(); importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()); importHelper.assignTask("Test", case2.getStringId(), user1.transformToLoggedUser()); importHelper.finishTask("Test", case2.getStringId(), user1.transformToLoggedUser()); - String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String query = String.format("task: lastFinish eq %s", SearchUtils.toDateTimeString(task.getLastFinished())); - String queryBefore = String.format("task: lastFinish > %s", SearchUtils.toDateTimeString(before)); - String queryMore = String.format("tasks: lastFinish > %s", SearchUtils.toDateTimeString(before)); - - long count = searchService.count(query); - assert count == 1; - - count = searchService.count(queryBefore); - assert count == 2; - - Object foundTask = searchService.search(query); - compareTasks(convertToTask(foundTask), task); - - foundTask = searchService.search(queryBefore); - compareTasks(convertToTask(foundTask), List.of(task, task2)); + String queryBefore = String.format("task: lastFinish gt %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastFinish gt %s", SearchUtils.toDateTimeString(before)); - Object foundTasks = searchService.search(queryMore); - compareTasks(convertToTaskList(foundTasks), List.of(task, task2)); + searchAndCompare(query, task); + searchAndCompare(queryBefore, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); } } diff --git a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java index f1ee5e9a658..b3422826246 100644 --- a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java +++ b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java @@ -51,7 +51,7 @@ public static <T> void compareByIdInOrder(List<T> actual, List<T> expected, Func .map(getId) .collect(Collectors.toList()); - assert actualIds.containsAll(expectedIds); + assert actualIds.equals(expectedIds); int lastIndex = -1; for (String expectedId : expectedIds) { From 2bb49aa174c65beec66277c5a96034c21f32906c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 12 Mar 2025 11:24:37 +0100 Subject: [PATCH 25/27] [NAE-1997] Query language - fix processIdentifier in range/in list elastic query - update query lang general tests elastic - add tasks sorting query --- .../engine/search/QueryLangEvaluator.java | 6 +- .../engine/search/utils/SearchUtils.java | 5 +- .../engine/search/QueryLangTest.java | 297 ++++++++++++------ .../engine/search/SearchTaskTest.java | 151 +++++++-- .../engine/search/utils/SearchTestUtils.java | 7 - 5 files changed, 345 insertions(+), 121 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 90cd6744a4f..12fd6091dba 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -671,6 +671,7 @@ public void exitProcessIdentifierList(QueryLangParser.ProcessIdentifierListConte List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream().map(node -> getStringValue(node.getText())).collect(Collectors.toList()); setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processIdentifier", stringList, not)); } @Override @@ -683,6 +684,7 @@ public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeCon String rightString = getStringValue(ctx.inRangeStringComparison().stringRange().STRING(1).getText()); setMongoQuery(ctx, buildStringPredicateInRange(stringPath, leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); + setElasticQuery(ctx, buildElasticQueryInRange("processIdentifier", leftString, leftEndpointOpen, rightString, rightEndpointOpen, not)); } @Override @@ -1198,12 +1200,12 @@ public void exitPlacesRange(QueryLangParser.PlacesRangeContext ctx) { String rightNumberAsString; if (ctx.inRangeNumberComparison().intRange() != null) { leftEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeNumberComparison().intRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().intRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftNumberAsString = ctx.inRangeNumberComparison().intRange().INT(0).getText(); rightNumberAsString = ctx.inRangeNumberComparison().intRange().INT(1).getText(); } else { leftEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(LEFT_OPEN_ENDPOINT); - rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().leftEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); + rightEndpointOpen = ctx.inRangeNumberComparison().doubleRange().rightEndpoint.getText().equals(RIGHT_OPEN_ENDPOINT); leftNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(0).getText(); rightNumberAsString = ctx.inRangeNumberComparison().doubleRange().DOUBLE(1).getText(); } diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index f67ecf90829..4cebdbe6479 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -36,12 +36,11 @@ public class SearchUtils { public static final Map<ComparisonType, List<Integer>> comparisonOperators = Map.of( ComparisonType.ID, List.of(QueryLangParser.EQ), - ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS), + ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.DATETIME, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), - ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ), - ComparisonType.OPTIONS, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS) + ComparisonType.BOOLEAN, List.of(QueryLangParser.EQ) ); public static final Map<String, String> processAttrToSortPropMapping = Map.of( diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 6d30218fcca..7a0caf79dd3 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -651,49 +651,23 @@ public void testSimpleElasticCaseQuery() { String expected = String.format("stringId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); + // todo NAE-1997: id in list + // processId comparison actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("processId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); - // processIdentifier comparison - actual = evaluateQuery("case: processIdentifier eq 'test'").getFullElasticQuery(); - expected = "processIdentifier:test"; - assert expected.equals(actual); + // todo NAE-1997: processId id in list - actual = evaluateQuery("case: processIdentifier contains 'test'").getFullElasticQuery(); - expected = "processIdentifier:*test*"; - assert expected.equals(actual); + // processIdentifier comparison + checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); // title comparison - actual = evaluateQuery("case: title eq 'test'").getFullElasticQuery(); - expected = "title:test"; - assert expected.equals(actual); - - actual = evaluateQuery("case: title contains 'test'").getFullElasticQuery(); - expected = "title:*test*"; - assert expected.equals(actual); + checkStringComparisonElastic("case", "title", "title"); // creationDate comparison - actual = evaluateQuery("case: creationDate eq 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("creationDateSortable:%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: creationDate lt 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("creationDateSortable:<%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: creationDate lte 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("creationDateSortable:<=%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: creationDate gt 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("creationDateSortable:>%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: creationDate gte 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("creationDateSortable:>=%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); + checkDateComparisonElastic("case", "creationDate", "creationDateSortable"); // author comparison actual = evaluateQuery("case: author eq 'test'").getFullElasticQuery(); @@ -704,10 +678,10 @@ public void testSimpleElasticCaseQuery() { expected = "author:*test*"; assert expected.equals(actual); + // todo NAE-1997: author in list + // places comparison - actual = evaluateQuery("case: places.p1.marking eq 1").getFullElasticQuery(); - expected = "places.p1.marking:1"; - assert expected.equals(actual); + checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); // task state comparison actual = evaluateQuery("case: tasks.t1.state eq enabled").getFullElasticQuery(); @@ -727,54 +701,14 @@ public void testSimpleElasticCaseQuery() { expected = "tasks.t1.userId:*test*"; assert expected.equals(actual); - // data value comparison - actual = evaluateQuery("case: data.field1.value eq 'test'").getFullElasticQuery(); - expected = "dataSet.field1.textValue:test"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value contains 'test'").getFullElasticQuery(); - expected = "dataSet.field1.textValue:*test*"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value eq 1").getFullElasticQuery(); - expected = "dataSet.field1.numberValue:1"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value lt 1").getFullElasticQuery(); - expected = "dataSet.field1.numberValue:<1"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value lte 1").getFullElasticQuery(); - expected = "dataSet.field1.numberValue:<=1"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value gt 1").getFullElasticQuery(); - expected = "dataSet.field1.numberValue:>1"; - assert expected.equals(actual); + // todo NAE-1997: task userId in list - actual = evaluateQuery("case: data.field1.value gte 1").getFullElasticQuery(); - expected = "dataSet.field1.numberValue:>=1"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value eq 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("dataSet.field1.timestampValue:%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value lt 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("dataSet.field1.timestampValue:<%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.value lte 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("dataSet.field1.timestampValue:<=%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); + // data value comparison + checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.textValue"); - actual = evaluateQuery("case: data.field1.value gt 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("dataSet.field1.timestampValue:>%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); + checkNumberComparisonElastic("case", "data.field2.value", "dataSet.field2.numberValue"); - actual = evaluateQuery("case: data.field1.value gte 2011-12-03T10:15:30").getFullElasticQuery(); - expected = String.format("dataSet.field1.timestampValue:>=%s", Timestamp.valueOf(localDateTime).getTime()); - assert expected.equals(actual); + checkDateComparisonElastic("case", "data.field3.value", "dataSet.field3.timestampValue"); actual = evaluateQuery("case: data.field1.value eq true").getFullElasticQuery(); expected = "dataSet.field1.booleanValue:true"; @@ -785,13 +719,7 @@ public void testSimpleElasticCaseQuery() { assert expected.equals(actual); // data options comparison - actual = evaluateQuery("case: data.field1.options eq 'test'").getFullElasticQuery(); - expected = "dataSet.field1.options:test"; - assert expected.equals(actual); - - actual = evaluateQuery("case: data.field1.options contains 'test'").getFullElasticQuery(); - expected = "dataSet.field1.options:*test*"; - assert expected.equals(actual); + checkStringComparisonElastic("case", "data.field1.options", "dataSet.field1.options"); } @Test @@ -1685,6 +1613,199 @@ private static void checkDateComparison(MongoDbUtils<?> mongoDbUtils, String res compareMongoQueries(mongoDbUtils, actual, expected); } + private static void checkStringComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 'test'", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s contains 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:*test*", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lte 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gt 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 'test'", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=test", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(test1 OR test2 OR test3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1', 'test2', 'test3')", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(test1 OR test2 OR test3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in ('test1' : 'test2')", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>test1 AND %s:<test2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in ['test1' : 'test2']", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in ('test1' : 'test2']", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>test1 AND %s:<=test2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + } + + private static void checkNumberComparisonElastic(String resource, String attribute, String resultAttribute) { + String actual = evaluateQuery(String.format("%s: %s eq 1", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gt 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 1", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=1", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(1 OR 2 OR 3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (1, 2, 3)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(1 OR 2 OR 3)", resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (1 : 2)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>1 AND %s:<2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=1 AND %s:<=2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (1 : 2]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>1 AND %s:<=2)", resultAttribute, resultAttribute); + + assert actual.equals(expected); + } + + private static void checkDateComparisonElastic(String resource, String attribute, String resultAttribute) { + LocalDateTime date1 = LocalDateTime.of(2011, 12, 3, 10, 15, 30); + LocalDateTime date2 = LocalDateTime.of(2011, 12, 3, 11, 15, 30); + LocalDateTime date3 = LocalDateTime.of(2011, 12, 3, 12, 15, 30); + LocalDateTime date4 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date5 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + LocalDateTime date6 = LocalDateTime.of(2011, 12, 3, 12, 0, 0); + + String actual = evaluateQuery(String.format("%s: %s eq 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + String expected = String.format("%s:%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s lte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:<=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gt 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s gte 2011-12-03T10:15:30", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:>=%s", resultAttribute, Timestamp.valueOf(date1).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30, 2011-12-03T11:15:30, 2011-12-03T12:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date1).getTime(), Timestamp.valueOf(date2).getTime(), Timestamp.valueOf(date3).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03T10:15:30 : 2011-12-03T11:15:30)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03T10:15:30 : 2011-12-03T11:15:30]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date1).getTime(), resultAttribute, Timestamp.valueOf(date2).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("%s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03, 2011-12-03, 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT %s:(%s OR %s OR %s)", resultAttribute, Timestamp.valueOf(date4).getTime(), Timestamp.valueOf(date5).getTime(), Timestamp.valueOf(date6).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in (2011-12-03 : 2011-12-03)", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>%s AND %s:<%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s in [2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("(%s:>=%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + + actual = evaluateQuery(String.format("%s: %s not in (2011-12-03 : 2011-12-03]", resource, attribute)).getFullElasticQuery(); + expected = String.format("NOT (%s:>%s AND %s:<=%s)", resultAttribute, Timestamp.valueOf(date4).getTime(), resultAttribute, Timestamp.valueOf(date5).getTime()); + + assert actual.equals(expected); + } + private static void compareMongoQueries(MongoDbUtils<?> mongoDbUtils, Predicate actual, Predicate expected) { Document actualDocument = mongoDbUtils.convertPredicateToDocument(actual); Document expectedDocument = mongoDbUtils.convertPredicateToDocument(expected); diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index 89c934a3b98..08f50cf5cc9 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -4,6 +4,7 @@ import com.netgrif.application.engine.auth.domain.Authority; import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.User; +import com.netgrif.application.engine.petrinet.domain.I18nString; import com.netgrif.application.engine.petrinet.domain.PetriNet; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; @@ -24,6 +25,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -64,9 +66,9 @@ private PetriNet importPetriNet(String fileName) { return testNet; } - private IUser createUser(String name, String surname, String email, String authority) { + private IUser createUser(String name, String surname, String email) { User user = new User(email, "password", name, surname); - Authority[] authorities = new Authority[]{auths.get(authority)}; + Authority[] authorities = new Authority[]{auths.get("user")}; ProcessRole[] processRoles = new ProcessRole[]{}; return importHelper.createUser(user, authorities, processRoles); } @@ -113,17 +115,34 @@ public void testSearchById() { String query = String.format("task: id eq '%s'", taskId); searchAndCompare(query, task); + + // sort + String querySort = String.format("tasks: caseId eq '%s' sort by id", caze.getStringId()); + String querySort2 = String.format("tasks: caseId eq '%s' sort by id desc", caze.getStringId()); + + List<Task> case1Tasks = caze.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List<Task> asc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList()); + List<Task> desc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getStringId).reversed()) + .collect(Collectors.toList()); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); } @Test public void testSearchByTransitionId() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); - Case caze = importHelper.createCase("Search Test", net); - Case caze2 = importHelper.createCase("Search Test2", net2); - String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + Case case1 = importHelper.createCase("Search Test", net); + Case case2 = importHelper.createCase("Search Test2", net2); + String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); - String task2Id = caze2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String query = String.format("task: transitionId eq '%s'", TEST_TRANSITION_ID); @@ -131,6 +150,23 @@ public void testSearchByTransitionId() { searchAndCompare(query, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: caseId eq '%s' sort by transitionId", case1.getStringId()); + String querySort2 = String.format("tasks: caseId eq '%s' sort by transitionId desc", case1.getStringId()); + + List<Task> case1Tasks = case1.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .collect(Collectors.toList()); + List<Task> asc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getTransitionId)) + .collect(Collectors.toList()); + List<Task> desc = case1Tasks.stream() + .sorted(Comparator.comparing(Task::getTransitionId).reversed()) + .collect(Collectors.toList()); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); } @Test @@ -145,18 +181,27 @@ public void testSearchByTitle() { Task task2 = taskService.findOne(task2Id); String task3Id = case3.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task3 = taskService.findOne(task3Id); + task3.setTitle(new I18nString("Zzz")); + taskService.save(task3); String query = String.format("task: title eq '%s'", task.getTitle().getDefaultValue()); String queryMore = String.format("tasks: title eq '%s'", task.getTitle().getDefaultValue()); - searchAndCompare(query, List.of(task, task2, task3)); - searchAndCompareAsList(queryMore, List.of(task, task2, task3)); + searchAndCompare(query, List.of(task, task2)); + searchAndCompareAsList(queryMore, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by title", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by title desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2, task3)); + searchAndCompareAsListInOrder(querySort2, List.of(task3, task, task2)); } @Test public void testSearchByState() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); Case case1 = importHelper.createCase("Search Test", net); importHelper.assignTask("Test", case1.getStringId(), user1.transformToLoggedUser()); case1 = importHelper.finishTask("Test", case1.getStringId(), user1.transformToLoggedUser()).getCase(); @@ -165,9 +210,11 @@ public void testSearchByState() { .collect(Collectors.toList()); List<Task> disabled = case1Tasks.stream() .filter(task -> task.getState().equals(State.DISABLED)) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); List<Task> enabled = case1Tasks.stream() .filter(task -> task.getState().equals(State.ENABLED)) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); String query = String.format("task: processId eq '%s' and state eq %s", net.getStringId(), "disabled"); @@ -177,13 +224,26 @@ public void testSearchByState() { searchAndCompare(query, disabled); searchAndCompareAsList(queryOther, enabled); searchAndCompareAsList(queryMore, disabled); + + // sort + String querySort = String.format("tasks: processId eq '%s' sort by state", net.getStringId()); + String querySort2 = String.format("tasks: processId eq '%s' sort by state desc", net.getStringId()); + + List<Task> asc = new ArrayList<>(); + asc.addAll(disabled); + asc.addAll(enabled); + List<Task> desc = new ArrayList<>(); + desc.addAll(enabled); + desc.addAll(disabled); + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); } @Test public void testSearchByUserId() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); - IUser user2 = createUser("Name2", "Surname2", "Email2", "user"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); + IUser user2 = createUser("Name2", "Surname2", "Email2"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); Case case3 = importHelper.createCase("Search Test2", net); @@ -204,6 +264,13 @@ public void testSearchByUserId() { searchAndCompare(query, List.of(task, task2)); searchAndCompare(queryOther, List.of(task3)); searchAndCompareAsList(queryMore, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by userId", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by userId desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2, task3)); + searchAndCompareAsListInOrder(querySort2, List.of(task3, task, task2)); } @Test @@ -213,9 +280,11 @@ public void testSearchByCaseId() { Case case2 = importHelper.createCase("Search Test", net); List<Task> case1Tasks = case1.getTasks().values().stream() .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); List<Task> case2Tasks = case2.getTasks().values().stream() .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); String query = String.format("task: caseId eq '%s'", case1.getStringId()); @@ -225,6 +294,20 @@ public void testSearchByCaseId() { searchAndCompare(query, case1Tasks); searchAndCompare(queryOther, case2Tasks); searchAndCompareAsList(queryMore, case1Tasks); + + // sort + String querySort = String.format("tasks: processId eq '%s' sort by caseId", net.getStringId()); + String querySort2 = String.format("tasks: processId eq '%s' sort by caseId desc", net.getStringId()); + + List<Task> asc = new ArrayList<>(); + asc.addAll(case1Tasks); + asc.addAll(case2Tasks); + List<Task> desc = new ArrayList<>(); + desc.addAll(case2Tasks); + desc.addAll(case1Tasks); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); } @Test @@ -232,16 +315,14 @@ public void testSearchByProcessId() { PetriNet net = importPetriNet("search/search_test.xml"); PetriNet net2 = importPetriNet("search/search_test2.xml"); Case case1 = importHelper.createCase("Search Test", net); - Case case2 = importHelper.createCase("Search Test", net); - Case case3 = importHelper.createCase("Search Test2", net2); + Case case2 = importHelper.createCase("Search Test2", net2); List<Task> netTasks = case1.getTasks().values().stream() .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); - netTasks.addAll(case2.getTasks().values().stream() - .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) - .collect(Collectors.toList())); - List<Task> net2Tasks = case3.getTasks().values().stream() + List<Task> net2Tasks = case2.getTasks().values().stream() .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) .collect(Collectors.toList()); String query = String.format("task: processId eq '%s'", net.getStringId()); @@ -251,12 +332,26 @@ public void testSearchByProcessId() { searchAndCompare(query, netTasks); searchAndCompare(queryOther, net2Tasks); searchAndCompareAsList(queryMore, netTasks); + + // sort + String querySort = String.format("tasks: processId eq '%s' or processId eq '%s' sort by processId", net.getStringId(), net2.getStringId()); + String querySort2 = String.format("tasks: processId eq '%s' or processId eq '%s' sort by processId desc", net.getStringId(), net2.getStringId()); + + List<Task> asc = new ArrayList<>(); + asc.addAll(netTasks); + asc.addAll(net2Tasks); + List<Task> desc = new ArrayList<>(); + desc.addAll(net2Tasks); + desc.addAll(netTasks); + + searchAndCompareAsListInOrder(querySort, asc); + searchAndCompareAsListInOrder(querySort2, desc); } @Test public void testSearchByLastAssign() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); LocalDateTime before = LocalDateTime.now(); @@ -268,18 +363,25 @@ public void testSearchByLastAssign() { Task task2 = taskService.findOne(task2Id); String query = String.format("task: lastAssign eq %s", SearchUtils.toDateTimeString(task.getLastAssigned())); - String queryBefore = String.format("task: lastAssign gt %s", SearchUtils.toDateTimeString(before)); - String queryMore = String.format("tasks: lastAssign gt %s", SearchUtils.toDateTimeString(before)); + String queryBefore = String.format("task: lastAssign > %s", SearchUtils.toDateTimeString(before)); + String queryMore = String.format("tasks: lastAssign > %s", SearchUtils.toDateTimeString(before)); searchAndCompare(query, task); searchAndCompare(queryBefore, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by lastAssign", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastAssign desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2)); + searchAndCompareAsListInOrder(querySort2, List.of(task2, task)); } @Test public void testSearchByLastFinish() { PetriNet net = importPetriNet("search/search_test.xml"); - IUser user1 = createUser("Name1", "Surname1", "Email1", "user"); + IUser user1 = createUser("Name1", "Surname1", "Email1"); Case case1 = importHelper.createCase("Search Test", net); Case case2 = importHelper.createCase("Search Test", net); LocalDateTime before = LocalDateTime.now(); @@ -299,5 +401,12 @@ public void testSearchByLastFinish() { searchAndCompare(query, task); searchAndCompare(queryBefore, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + + // sort + String querySort = String.format("tasks: transitionId eq '%s' sort by lastFinish", TEST_TRANSITION_ID); + String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastFinish desc", TEST_TRANSITION_ID); + + searchAndCompareAsListInOrder(querySort, List.of(task, task2)); + searchAndCompareAsListInOrder(querySort2, List.of(task2, task)); } } diff --git a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java index b3422826246..93d9d80ecee 100644 --- a/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java +++ b/src/test/java/com/netgrif/application/engine/search/utils/SearchTestUtils.java @@ -52,12 +52,5 @@ public static <T> void compareByIdInOrder(List<T> actual, List<T> expected, Func .collect(Collectors.toList()); assert actualIds.equals(expectedIds); - - int lastIndex = -1; - for (String expectedId : expectedIds) { - int currentIndex = actualIds.indexOf(expectedId); - assert currentIndex > lastIndex; - lastIndex = currentIndex; - } } } From 0fe0a5b2f43b5ac5f70c88f075c731a9e4a02302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 12 Mar 2025 12:31:47 +0100 Subject: [PATCH 26/27] [NAE-1997] Query language - add in list operator to more attributes --- .../engine/search/QueryLangEvaluator.java | 121 +++++++++++++++++- .../search/QueryLangExplainEvaluator.java | 7 +- .../engine/search/antlr4/QueryLang.g4 | 28 +++- .../engine/search/utils/SearchUtils.java | 8 +- .../engine/search/QueryLangTest.java | 63 ++++++++- 5 files changed, 205 insertions(+), 22 deletions(-) diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java index 12fd6091dba..72ca6548115 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangEvaluator.java @@ -363,7 +363,7 @@ public void exitUserComparisons(QueryLangParser.UserComparisonsContext ctx) { } @Override - public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { + public void exitIdBasic(QueryLangParser.IdBasicContext ctx) { QObjectId qObjectId; Token op = ctx.objectIdComparison().op; boolean not = ctx.objectIdComparison().NOT() != null; @@ -391,6 +391,40 @@ public void exitIdComparison(QueryLangParser.IdComparisonContext ctx) { setMongoQuery(ctx, buildObjectIdPredicate(qObjectId, op.getType(), objectId, not)); } + @Override + public void exitIdList(QueryLangParser.IdListContext ctx) { + QObjectId qObjectId; + Token op = ctx.inListStringComparison().op; + boolean not = ctx.inListStringComparison().NOT() != null; + checkOp(ComparisonType.ID, op); + List<ObjectId> objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List<String> stringIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + switch (type) { + case PROCESS: + qObjectId = QPetriNet.petriNet.id; + break; + case CASE: + qObjectId = QCase.case$.id; + setElasticQuery(ctx, buildElasticQueryInList("stringId", stringIdList, not)); + break; + case TASK: + qObjectId = QTask.task.id; + break; + case USER: + qObjectId = QUser.user.id; + break; + default: + throw new IllegalArgumentException("Unknown query type: " + type); + } + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + } + @Override public void exitTitleBasic(QueryLangParser.TitleBasicContext ctx) { StringPath stringPath; @@ -633,7 +667,7 @@ public void exitCdDateRange(QueryLangParser.CdDateRangeContext ctx) { } @Override - public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext ctx) { + public void exitProcessIdBasic(QueryLangParser.ProcessIdBasicContext ctx) { StringPath stringPath = QTask.task.processId; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; @@ -643,7 +677,18 @@ public void exitProcessIdComparison(QueryLangParser.ProcessIdComparisonContext c } @Override - public void exitProcessIdObjIdComparison(QueryLangParser.ProcessIdObjIdComparisonContext ctx) { + public void exitProcessIdList(QueryLangParser.ProcessIdListContext ctx) { + StringPath stringPath = QTask.task.processId; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitProcessIdObjIdBasic(QueryLangParser.ProcessIdObjIdBasicContext ctx) { QObjectId qObjectId = QCase.case$.petriNetObjectId; Token op = ctx.objectIdComparison().op; boolean not = ctx.objectIdComparison().NOT() != null; @@ -653,6 +698,21 @@ public void exitProcessIdObjIdComparison(QueryLangParser.ProcessIdObjIdCompariso setElasticQuery(ctx, buildElasticQuery("processId", op.getType(), objectId.toString(), not)); } + @Override + public void exitProcessIdObjIdList(QueryLangParser.ProcessIdObjIdListContext ctx) { + QObjectId qObjectId = QCase.case$.petriNetObjectId; + boolean not = ctx.inListStringComparison().NOT() != null; + List<ObjectId> objectIdList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getObjectIdValue(node.getText())) + .collect(Collectors.toList()); + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildObjectIdPredicateInList(qObjectId, objectIdList, not)); + setElasticQuery(ctx, buildElasticQueryInList("processId", stringList, not)); + } + @Override public void exitProcessIdentifierBasic(QueryLangParser.ProcessIdentifierBasicContext ctx) { StringPath stringPath = QCase.case$.processIdentifier; @@ -688,7 +748,7 @@ public void exitProcessIdentifierRange(QueryLangParser.ProcessIdentifierRangeCon } @Override - public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { + public void exitAuthorBasic(QueryLangParser.AuthorBasicContext ctx) { StringPath stringPath = QCase.case$.author.id; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; @@ -698,6 +758,18 @@ public void exitAuthorComparison(QueryLangParser.AuthorComparisonContext ctx) { setElasticQuery(ctx, buildElasticQuery("author", op.getType(), string, not)); } + @Override + public void exitAuthorList(QueryLangParser.AuthorListContext ctx) { + StringPath stringPath = QCase.case$.author.id; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + setElasticQuery(ctx, buildElasticQueryInList("author", stringList, not)); + } + @Override public void exitTransitionIdBasic(QueryLangParser.TransitionIdBasicContext ctx) { StringPath stringPath = QTask.task.transitionId; @@ -742,7 +814,7 @@ public void exitStateComparison(QueryLangParser.StateComparisonContext ctx) { } @Override - public void exitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { + public void exitUserIdBasic(QueryLangParser.UserIdBasicContext ctx) { StringPath stringPath = QTask.task.userId; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; @@ -752,7 +824,18 @@ public void exitUserIdComparison(QueryLangParser.UserIdComparisonContext ctx) { } @Override - public void exitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { + public void exitUserIdList(QueryLangParser.UserIdListContext ctx) { + StringPath stringPath = QTask.task.userId; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + + @Override + public void exitCaseIdBasic(QueryLangParser.CaseIdBasicContext ctx) { StringPath stringPath = QTask.task.caseId; Token op = ctx.stringComparison().op; boolean not = ctx.stringComparison().NOT() != null; @@ -761,6 +844,17 @@ public void exitCaseIdComparison(QueryLangParser.CaseIdComparisonContext ctx) { setMongoQuery(ctx, buildStringPredicate(stringPath, op.getType(), string, not)); } + @Override + public void exitCaseIdList(QueryLangParser.CaseIdListContext ctx) { + StringPath stringPath = QTask.task.caseId; + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, buildStringPredicateInList(stringPath, stringList, not)); + } + @Override public void exitLaDateBasic(QueryLangParser.LaDateBasicContext ctx) { DateTimePath<LocalDateTime> dateTimePath = QTask.task.lastAssigned; @@ -1229,7 +1323,7 @@ public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext } @Override - public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonContext ctx) { + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { String taskId = ctx.tasksUserId().taskId.getText(); Token op = ctx.stringComparison().op; checkOp(ComparisonType.STRING, op); @@ -1241,6 +1335,19 @@ public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonConte this.searchWithElastic = true; } + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { + String taskId = ctx.tasksUserId().taskId.getText(); + boolean not = ctx.inListStringComparison().NOT() != null; + List<String> stringList = ctx.inListStringComparison().stringList().STRING().stream() + .map(node -> getStringValue(node.getText())) + .collect(Collectors.toList()); + + setMongoQuery(ctx, null); + setElasticQuery(ctx, buildElasticQueryInList("tasks." + taskId + ".userId", stringList, not)); + this.searchWithElastic = true; + } + @Override public void exitPaging(QueryLangParser.PagingContext ctx) { pageNumber = Integer.parseInt(ctx.pageNum.getText()); diff --git a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java index 239a9425bd7..c02f6cc586b 100644 --- a/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java +++ b/src/main/java/com/netgrif/application/engine/search/QueryLangExplainEvaluator.java @@ -352,7 +352,12 @@ public void exitTasksStateComparison(QueryLangParser.TasksStateComparisonContext } @Override - public void exitTasksUserIdComparison(QueryLangParser.TasksUserIdComparisonContext ctx) { + public void exitTasksUserIdBasic(QueryLangParser.TasksUserIdBasicContext ctx) { + searchWithElastic = true; + } + + @Override + public void exitTasksUserIdList(QueryLangParser.TasksUserIdListContext ctx) { searchWithElastic = true; } diff --git a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 index 5329a2dd5c2..88c455c6c03 100644 --- a/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 +++ b/src/main/java/com/netgrif/application/engine/search/antlr4/QueryLang.g4 @@ -131,7 +131,9 @@ userComparisons: idComparison ; // attribute comparisons -idComparison: ID SPACE objectIdComparison ; +idComparison: ID SPACE objectIdComparison # idBasic + | ID SPACE inListStringComparison # idList + ; titleComparison: TITLE SPACE stringComparison # titleBasic | TITLE SPACE inListStringComparison # titleList | TITLE SPACE inRangeStringComparison # titleRange @@ -149,20 +151,30 @@ creationDateComparison: CREATION_DATE SPACE dateComparison # cdDateBasic | CREATION_DATE SPACE inListDateComparison # cdDateList | CREATION_DATE SPACE inRangeDateComparison # cdDateRange ; -processIdComparison: PROCESS_ID SPACE stringComparison ; -processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison ; +processIdComparison: PROCESS_ID SPACE stringComparison # processIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdList + ; +processIdObjIdComparison: PROCESS_ID SPACE objectIdComparison # processIdObjIdBasic + | PROCESS_ID SPACE inListStringComparison # processIdObjIdList + ; processIdentifierComparison: PROCESS_IDENTIFIER SPACE stringComparison # processIdentifierBasic | PROCESS_IDENTIFIER SPACE inListStringComparison # processIdentifierList | PROCESS_IDENTIFIER SPACE inRangeStringComparison # processIdentifierRange ; -authorComparison: AUTHOR SPACE stringComparison ; +authorComparison: AUTHOR SPACE stringComparison # authorBasic + | AUTHOR SPACE inListStringComparison # authorList + ; transitionIdComparison: TRANSITION_ID SPACE stringComparison # transitionIdBasic | TRANSITION_ID SPACE inListStringComparison # transitionIdList | TRANSITION_ID SPACE inRangeStringComparison # transitionIdRange ; stateComparison: STATE SPACE EQ SPACE state=(ENABLED | DISABLED) ; -userIdComparison: USER_ID SPACE stringComparison ; -caseIdComparison: CASE_ID SPACE stringComparison ; +userIdComparison: USER_ID SPACE stringComparison # userIdBasic + | USER_ID SPACE inListStringComparison # userIdList + ; +caseIdComparison: CASE_ID SPACE stringComparison # caseIdBasic + | CASE_ID SPACE inListStringComparison # caseIdList + ; lastAssignComparison: LAST_ASSIGN SPACE dateComparison # laDateBasic | LAST_ASSIGN SPACE dateTimeComparison # laDateTimeBasic | LAST_ASSIGN SPACE inListDateComparison # laDateList @@ -206,7 +218,9 @@ placesComparison: places SPACE numberComparison # placesBasic | places SPACE inRangeNumberComparison # placesRange ; tasksStateComparison: tasksState SPACE (NOT SPACE?)? op=EQ SPACE state=(ENABLED | DISABLED) ; -tasksUserIdComparison: tasksUserId SPACE stringComparison ; +tasksUserIdComparison: tasksUserId SPACE stringComparison # tasksUserIdBasic + | tasksUserId SPACE inListStringComparison # tasksUserIdList + ; // basic comparisons objectIdComparison: (NOT SPACE?)? op=EQ SPACE STRING ; diff --git a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java index 4cebdbe6479..2d88ad0ef87 100644 --- a/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java +++ b/src/main/java/com/netgrif/application/engine/search/utils/SearchUtils.java @@ -35,7 +35,7 @@ public class SearchUtils { public static final Map<ComparisonType, List<Integer>> comparisonOperators = Map.of( - ComparisonType.ID, List.of(QueryLangParser.EQ), + ComparisonType.ID, List.of(QueryLangParser.EQ, QueryLangParser.IN), ComparisonType.STRING, List.of(QueryLangParser.EQ, QueryLangParser.CONTAINS, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.NUMBER, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), ComparisonType.DATE, List.of(QueryLangParser.EQ, QueryLangParser.LT, QueryLangParser.LTE, QueryLangParser.GT, QueryLangParser.GTE), @@ -207,6 +207,12 @@ public static Predicate buildObjectIdPredicate(QObjectId qObjectId, int op, Obje return predicate; } + public static Predicate buildObjectIdPredicateInList(QObjectId qObjectId, List<ObjectId> values, boolean not) { + Predicate predicate = qObjectId.in(values); + + return not ? predicate.not() : predicate; + } + public static Predicate buildStringPredicate(StringPath stringPath, int op, String string, boolean not) { Predicate predicate = null; switch (op) { diff --git a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java index 7a0caf79dd3..57d85b6ec73 100644 --- a/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java +++ b/src/test/java/com/netgrif/application/engine/search/QueryLangTest.java @@ -51,6 +51,11 @@ public void testSimpleMongodbProcessQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("process: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QPetriNet.petriNet.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + // identifier comparison checkStringComparison(mongoDbUtils, "process", "identifier", QPetriNet.petriNet.identifier); @@ -209,12 +214,22 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + // processId comparison actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullMongoQuery(); expected = QCase.case$.petriNetObjectId.eq(GENERIC_OBJECT_ID); compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QCase.case$.petriNetObjectId.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + // processIdentifier comparison checkStringComparison(mongoDbUtils, "case", "processIdentifier", QCase.case$.processIdentifier); @@ -235,6 +250,11 @@ public void testSimpleMongodbCaseQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("case: author in ('test', 'test1')").getFullMongoQuery(); + expected = QCase.case$.author.id.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + // only available for elastic query // places comparison actual = evaluateQuery("case: places.p1.marking eq 1").getFullMongoQuery(); @@ -355,6 +375,11 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("task: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QTask.task.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + // transitionId comparison checkStringComparison(mongoDbUtils, "task", "transitionId", QTask.task.transitionId); @@ -383,6 +408,11 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: userId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.userId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + // caseId comparison actual = evaluateQuery("task: caseId eq 'test'").getFullMongoQuery(); expected = QTask.task.caseId.eq("test"); @@ -394,6 +424,11 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: caseId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.caseId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + // processId comparison actual = evaluateQuery("task: processId eq 'test'").getFullMongoQuery(); expected = QTask.task.processId.eq("test"); @@ -405,6 +440,11 @@ public void testSimpleMongodbTaskQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery("task: processId in ('test', 'test1')").getFullMongoQuery(); + expected = QTask.task.processId.in("test", "test1"); + + compareMongoQueries(mongoDbUtils, actual, expected); + // lastAssign comparison checkDateComparison(mongoDbUtils, "task", "lastAssign", QTask.task.lastAssigned); @@ -477,6 +517,11 @@ public void testSimpleMongodbUserQuery() { compareMongoQueries(mongoDbUtils, actual, expected); + actual = evaluateQuery(String.format("user: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullMongoQuery(); + expected = QUser.user.id.in(GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + + compareMongoQueries(mongoDbUtils, actual, expected); + // name comparison checkStringComparison(mongoDbUtils, "user", "name", QUser.user.name); @@ -644,21 +689,23 @@ public void testComplexElasticProcessQuery() { @Test public void testSimpleElasticCaseQuery() { - LocalDateTime localDateTime = LocalDateTime.of(2011, 12, 3, 10, 15, 30); - // id comparison String actual = evaluateQuery(String.format("case: id eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); String expected = String.format("stringId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); - // todo NAE-1997: id in list + actual = evaluateQuery(String.format("case: id in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("stringId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); // processId comparison actual = evaluateQuery(String.format("case: processId eq '%s'", GENERIC_OBJECT_ID)).getFullElasticQuery(); expected = String.format("processId:%s", GENERIC_OBJECT_ID); assert expected.equals(actual); - // todo NAE-1997: processId id in list + actual = evaluateQuery(String.format("case: processId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("processId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); // processIdentifier comparison checkStringComparisonElastic("case", "processIdentifier", "processIdentifier"); @@ -678,7 +725,9 @@ public void testSimpleElasticCaseQuery() { expected = "author:*test*"; assert expected.equals(actual); - // todo NAE-1997: author in list + actual = evaluateQuery(String.format("case: author in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("author:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); // places comparison checkNumberComparisonElastic("case", "places.p1.marking", "places.p1.marking"); @@ -701,7 +750,9 @@ public void testSimpleElasticCaseQuery() { expected = "tasks.t1.userId:*test*"; assert expected.equals(actual); - // todo NAE-1997: task userId in list + actual = evaluateQuery(String.format("case: tasks.t1.userId in ('%s', '%s')", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID)).getFullElasticQuery(); + expected = String.format("tasks.t1.userId:(%s OR %s)", GENERIC_OBJECT_ID, GENERIC_OBJECT_ID); + assert expected.equals(actual); // data value comparison checkStringComparisonElastic("case", "data.field1.value", "dataSet.field1.textValue"); From b1e0788c062fb29d0bc43fdec450b0aac4b2ca0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ir=C3=A1=C5=88?= <siran@netgrif.com> Date: Wed, 12 Mar 2025 16:15:41 +0100 Subject: [PATCH 27/27] [NAE-1997] Query language - add tests for tasks, users (in list/in range, sorting) - add missing tests for cases in list/in range --- .../engine/search/SearchCaseTest.java | 20 ++ .../engine/search/SearchProcessTest.java | 230 +++++------------- .../engine/search/SearchTaskTest.java | 106 +++++++- .../engine/search/SearchUserTest.java | 167 +++++++++---- 4 files changed, 302 insertions(+), 221 deletions(-) diff --git a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java index 58f07a50818..00f7b23399d 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchCaseTest.java @@ -113,6 +113,11 @@ public void testSearchById() { searchAndCompare(query, case1); + // in list + String queryInList = String.format("cases: id in ('%s', '%s')", case1.getStringId(), case2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2)); + // sort String querySort = String.format("cases: processIdentifier eq '%s' sort by id", net.getIdentifier()); String querySort2 = String.format("cases: processIdentifier eq '%s' sort by id desc", net.getIdentifier()); @@ -137,6 +142,11 @@ public void testSearchByProcessId() { searchAndCompare(queryOther, case3); searchAndCompareAsList(queryMore, List.of(case1, case2)); + // in list + String queryInList = String.format("cases: processId in ('%s', '%s')", net.getStringId(), net2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + // sort String querySort = String.format("cases: processIdentifier in ('%s', '%s') sort by processId", net.getIdentifier(), net2.getIdentifier()); String querySort2 = String.format("cases: processIdentifier in ('%s', '%s') sort by processId desc", net.getIdentifier(), net2.getIdentifier()); @@ -262,6 +272,11 @@ public void testSearchByAuthor() { searchAndCompare(queryOther, case3); searchAndCompareAsList(queryMore, List.of(case1, case2)); + // in list + String queryInList = String.format("cases: author in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + // sort String querySort = String.format("cases: processIdentifier eq '%s' sort by author", net.getIdentifier()); String querySort2 = String.format("cases: processIdentifier eq '%s' sort by author desc", net.getIdentifier()); @@ -361,6 +376,11 @@ public void testSearchByTaskUserId() { // todo NAE-1997: not indexing tasks.user searchAndCompare(queryOther, case3); searchAndCompareAsList(queryMore, List.of(case1, case2)); + // in list + String queryInList = String.format("cases: tasks.%s.userId in ('%s', '%s')", TEST_TRANSITION_ID, user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(case1, case2, case3)); + // sort String querySort = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId", net.getIdentifier(), TEST_TRANSITION_ID); String querySort2 = String.format("cases: processIdentifier eq '%s' sort by tasks.%s.userId desc", net.getIdentifier(), TEST_TRANSITION_ID); diff --git a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java index e498634099e..64971aa1025 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchProcessTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.stream.Collectors; +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; @Slf4j @@ -55,49 +56,36 @@ private PetriNet importPetriNet(String fileName, VersionType versionType) { return testNet; } - private static PetriNet convertToPetriNet(Object petriNetObject) { - assert petriNetObject instanceof PetriNet; - return (PetriNet) petriNetObject; - } - - private static List<PetriNet> convertToPetriNetList(Object petriNetListObject) { - assert petriNetListObject instanceof List<?>; - for (Object petriNetObject : (List<?>) petriNetListObject) { - assert petriNetObject instanceof PetriNet; - } - - return (List<PetriNet>) petriNetListObject; - } + private void searchAndCompare(String query, PetriNet expected) { + long count = searchService.count(query); + assert count == 1; - private void comparePetriNets(PetriNet actual, PetriNet expected) { - assert actual.getStringId().equals(expected.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObject(actual, PetriNet.class), expected, PetriNet::getStringId); } - private void comparePetriNets(PetriNet actual, List<PetriNet> expected) { - List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + private void searchAndCompare(String query, List<PetriNet> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert expectedStringIds.contains(actual.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObject(actual, PetriNet.class), expected, PetriNet::getStringId); } - private void comparePetriNets(List<PetriNet> actual, List<PetriNet> expected) { - List<String> actualStringIds = actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()); - List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsList(String query, List<PetriNet> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert actualStringIds.containsAll(expectedStringIds); + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, PetriNet.class), expected, PetriNet::getStringId); } - private void comparePetriNetsInOrder(List<PetriNet> actual, List<PetriNet> expected) { - List<String> actualStringIds = actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()); - List<String> expectedStringIds = expected.stream().map(PetriNet::getStringId).collect(Collectors.toList()); - - assert actualStringIds.containsAll(expectedStringIds); + private void searchAndCompareAsListInOrder(String query, List<PetriNet> expected) { + long count = searchService.count(query); + assert count == expected.size(); - int lastIndex = -1; - for (String expectedId : expectedStringIds) { - int currentIndex = actualStringIds.indexOf(expectedId); - assert currentIndex > lastIndex; - lastIndex = currentIndex; - } + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, PetriNet.class), expected, PetriNet::getStringId); } @Test @@ -107,20 +95,19 @@ public void testSearchById() { String query = String.format("process: id eq '%s'", net.getStringId()); - long count = searchService.count(query); - assert count == 1; + searchAndCompare(query, net); - Object process = searchService.search(query); + // in list + String queryInList = String.format("processes: id in ('%s', '%s')", net.getStringId(), net2.getStringId()); - comparePetriNets(convertToPetriNet(process), net); + searchAndCompareAsList(queryInList, List.of(net, net2)); + // sort query = String.format("processes: identifier eq '%s' sort by id", net.getIdentifier()); - Object processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, net2)); + searchAndCompareAsListInOrder(query, List.of(net, net2)); query = String.format("processes: identifier eq '%s' sort by id desc", net.getIdentifier()); - processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net)); + searchAndCompareAsListInOrder(query, List.of(net2, net)); } @Test @@ -133,33 +120,23 @@ public void testSearchByIdentifier() { String query = String.format("process: identifier eq '%s'", net.getIdentifier()); String queryMore = String.format("processes: identifier eq '%s'", net.getIdentifier()); - long count = searchService.count(query); - assert count == 2; - - Object process = searchService.search(query); - comparePetriNets(convertToPetriNet(process), List.of(net, netNewer)); - - Object processes = searchService.search(queryMore); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + searchAndCompare(query, List.of(net, netNewer)); + searchAndCompareAsList(queryMore, List.of(net, netNewer)); // in list String queryInList = String.format("processes: identifier in ('%s', '%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net3.getIdentifier()); - processes = searchService.search(queryInList); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2, net3)); + searchAndCompareAsList(queryInList, List.of(net, netNewer, net2, net3)); // in range - String queryInRange = String.format("processes: identifier in <'%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); - processes = searchService.search(queryInRange); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + String queryInRange = String.format("processes: identifier in ['%s' : '%s')", net.getIdentifier(), net3.getIdentifier()); + searchAndCompareAsList(queryInRange, List.of(net, netNewer, net2)); // sort queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier", net.getIdentifier(), net2.getIdentifier()); - processes = searchService.search(queryMore); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + searchAndCompareAsListInOrder(queryMore, List.of(net, netNewer, net2)); queryMore = String.format("processes: identifier in ('%s', '%s') sort by identifier desc", net.getIdentifier(), net2.getIdentifier()); - processes = searchService.search(queryMore); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net, netNewer)); + searchAndCompareAsListInOrder(queryMore, List.of(net2, net, netNewer)); } @Test @@ -175,58 +152,26 @@ public void testSearchByVersion() { String queryGt = String.format("processes: identifier eq '%s' and version gt %s", net.getIdentifier(), "1.0.0"); String queryGte = String.format("processes: identifier eq '%s' and version gte %s", net.getIdentifier(), "1.0.0"); - long count = searchService.count(queryEq); - assert count == 1; - - Object process = searchService.search(queryEq); - comparePetriNets(convertToPetriNet(process), List.of(net)); - - count = searchService.count(queryLt); - assert count == 3; - - Object processes = searchService.search(queryLt); - List<PetriNet> actual = convertToPetriNetList(processes); - comparePetriNets(actual, List.of(net, netNewerPatch, netNewerMinor)); - assert !actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()).contains(netNewerMajor.getStringId()); - - count = searchService.count(queryLte); - assert count == 4; - - processes = searchService.search(queryLte); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); - - count = searchService.count(queryGt); - assert count == 3; - - processes = searchService.search(queryGt); - actual = convertToPetriNetList(processes); - comparePetriNets(actual, List.of(netNewerPatch, netNewerMinor, netNewerMajor)); - assert !actual.stream().map(PetriNet::getStringId).collect(Collectors.toList()).contains(net.getStringId()); - - count = searchService.count(queryGte); - assert count == 4; - - processes = searchService.search(queryGte); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompare(queryEq, net); + searchAndCompareAsList(queryLt, List.of(net, netNewerPatch, netNewerMinor)); + searchAndCompareAsList(queryLte, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompareAsList(queryGt, List.of(netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompareAsList(queryGte, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); // in list String queryInList = String.format("processes: identifier eq '%s' and version in (%s, %s, %s)", net.getIdentifier(), "1.0.0", "1.0.1", "1.1.0"); - processes = searchService.search(queryInList); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor)); + searchAndCompareAsList(queryInList, List.of(net, netNewerPatch, netNewerMinor)); // in range - String queryInRange = String.format("processes: identifier eq '%s' and version in <%s : %s)", net.getIdentifier(), "1.0.0", "1.1.0"); - processes = searchService.search(queryInRange); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewerPatch)); + String queryInRange = String.format("processes: identifier eq '%s' and version in [%s : %s)", net.getIdentifier(), "1.0.0", "1.1.0"); + searchAndCompareAsList(queryInRange, List.of(net, netNewerPatch)); // sort String query = String.format("processes: identifier eq '%s' sort by version", net.getIdentifier()); - processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); + searchAndCompareAsListInOrder(query, List.of(net, netNewerPatch, netNewerMinor, netNewerMajor)); query = String.format("processes: identifier eq '%s' sort by version desc", net.getIdentifier()); - processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(netNewerMajor, netNewerMinor, netNewerPatch, net)); + searchAndCompareAsListInOrder(query, List.of(netNewerMajor, netNewerMinor, netNewerPatch, net)); } @Test @@ -239,39 +184,24 @@ public void testSearchByTitle() { String queryOther = String.format("process: title eq '%s'", net2.getTitle().toString()); String queryMore = String.format("processes: title eq '%s'", net.getTitle().toString()); - long count = searchService.count(query); - assert count == 2; - - Object process = searchService.search(query); - comparePetriNets(convertToPetriNet(process), List.of(net, netNewer)); - - count = searchService.count(queryOther); - assert count == 1; - - process = searchService.search(queryOther); - comparePetriNets(convertToPetriNet(process), net2); - - Object processes = searchService.search(queryMore); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + searchAndCompare(query, List.of(net, netNewer)); + searchAndCompare(queryOther, net2); + searchAndCompareAsList(queryMore, List.of(net, netNewer)); // in list String queryInList = String.format("processes: identifier in ('%s', '%s') and title in ('%s', '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); - processes = searchService.search(queryInList); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + searchAndCompareAsList(queryInList, List.of(net, netNewer, net2)); // in range - String queryInRange = String.format("processes: identifier in ('%s', '%s') and title in <'%s' : '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); - processes = searchService.search(queryInRange); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + String queryInRange = String.format("processes: identifier in ('%s', '%s') and title in ['%s' : '%s')", net.getIdentifier(), net2.getIdentifier(), net.getTitle().getDefaultValue(), net2.getTitle().getDefaultValue()); + searchAndCompareAsList(queryInRange, List.of(net, netNewer)); // sort queryMore = String.format("processes: identifier in ('%s', '%s') sort by title", net.getIdentifier(), net2.getIdentifier()); - processes = searchService.search(queryMore); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, net2)); + searchAndCompareAsListInOrder(queryMore, List.of(net, netNewer, net2)); queryMore = String.format("processes: identifier in ('%s', '%s') sort by title desc", net.getIdentifier(), net2.getIdentifier()); - processes = searchService.search(queryMore); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net2, net, netNewer)); + searchAndCompareAsListInOrder(queryMore, List.of(net2, net, netNewer)); } @Test @@ -286,54 +216,26 @@ public void testSearchByCreationDate() { String queryGt = String.format("processes: identifier eq '%s' and creationDate gt %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); String queryGte = String.format("processes: identifier eq '%s' and creationDate gte %s", net.getIdentifier(), toDateTimeString(net.getCreationDate())); - long count = searchService.count(queryEq); - assert count == 1; - - Object process = searchService.search(queryEq); - comparePetriNets(convertToPetriNet(process), net); - - count = searchService.count(queryLt); - assert count == 2; - - Object processes = searchService.search(queryLt); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); - - count = searchService.count(queryLte); - assert count == 3; - - processes = searchService.search(queryLte); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); - - count = searchService.count(queryGt); - assert count == 2; - - processes = searchService.search(queryGt); - comparePetriNets(convertToPetriNetList(processes), List.of(netNewer, netNewest)); - - count = searchService.count(queryGte); - assert count == 3; - - processes = searchService.search(queryGte); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); + searchAndCompare(queryEq, net); + searchAndCompareAsList(queryLt, List.of(net, netNewer)); + searchAndCompareAsList(queryLte, List.of(net, netNewer, netNewest)); + searchAndCompareAsList(queryGt, List.of(netNewer, netNewest)); + searchAndCompareAsList(queryGte, List.of(net, netNewer, netNewest)); // in list String queryInList = String.format("processes: identifier eq '%s' and creationDate in (%s, %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); - processes = searchService.search(queryInList); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewest)); + searchAndCompareAsList(queryInList, List.of(net, netNewest)); // in range - String queryInRange = String.format("processes: identifier eq '%s' and creationDate in <%s : %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); - processes = searchService.search(queryInRange); - comparePetriNets(convertToPetriNetList(processes), List.of(net, netNewer)); + String queryInRange = String.format("processes: identifier eq '%s' and creationDate in [%s : %s)", net.getIdentifier(), toDateTimeString(net.getCreationDate()), toDateTimeString(netNewest.getCreationDate())); + searchAndCompareAsList(queryInRange, List.of(net, netNewer)); // sort String query = String.format("processes: identifier eq '%s' sort by creationDate", net.getIdentifier()); - processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(net, netNewer, netNewest)); + searchAndCompareAsListInOrder(query, List.of(net, netNewer, netNewest)); query = String.format("processes: identifier eq '%s' sort by creationDate desc", net.getIdentifier()); - processes = searchService.search(query); - comparePetriNetsInOrder(convertToPetriNetList(processes), List.of(netNewest, netNewer, net)); + searchAndCompareAsListInOrder(query, List.of(netNewest, netNewer, net)); } @Test @@ -351,13 +253,13 @@ public void testPagination() { assert count == 50; Object process = searchService.search(queryOne); - comparePetriNets(convertToPetriNet(process), nets.get(0)); + compareById(convertToObject(process, PetriNet.class), nets.get(0), PetriNet::getStringId); Object processes = searchService.search(queryMore); - comparePetriNets(convertToPetriNetList(processes), nets.subList(0, 19)); + compareById(convertToObjectList(processes, PetriNet.class), nets.subList(0, 19), PetriNet::getStringId); processes = searchService.search(queryMoreCustomPagination); - comparePetriNets(convertToPetriNetList(processes), nets.subList(5, 9)); + compareById(convertToObjectList(processes, PetriNet.class), nets.subList(5, 9), PetriNet::getStringId); } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java index 08f50cf5cc9..165c675866a 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchTaskTest.java @@ -31,6 +31,7 @@ import java.util.stream.Collectors; import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; +import static com.netgrif.application.engine.search.utils.SearchUtils.toDateTimeString; @Slf4j @SpringBootTest @@ -111,11 +112,18 @@ public void testSearchById() { Case caze = importHelper.createCase("Search Test", net); String taskId = caze.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); + String task2Id = caze.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); + Task task2 = taskService.findOne(task2Id); String query = String.format("task: id eq '%s'", taskId); searchAndCompare(query, task); + // in list + String queryInList = String.format("tasks: id in ('%s', '%s')", task.getStringId(), task2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(task, task2)); + // sort String querySort = String.format("tasks: caseId eq '%s' sort by id", caze.getStringId()); String querySort2 = String.format("tasks: caseId eq '%s' sort by id desc", caze.getStringId()); @@ -137,19 +145,27 @@ public void testSearchById() { @Test public void testSearchByTransitionId() { PetriNet net = importPetriNet("search/search_test.xml"); - PetriNet net2 = importPetriNet("search/search_test2.xml"); Case case1 = importHelper.createCase("Search Test", net); - Case case2 = importHelper.createCase("Search Test2", net2); String taskId = case1.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); Task task = taskService.findOne(taskId); - String task2Id = case2.getTasks().get(TEST_TRANSITION_ID).getTaskStringId(); + String task2Id = case1.getTasks().get(TEST_TRANSITION2_ID).getTaskStringId(); Task task2 = taskService.findOne(task2Id); String query = String.format("task: transitionId eq '%s'", TEST_TRANSITION_ID); String queryMore = String.format("tasks: transitionId eq '%s'", TEST_TRANSITION_ID); - searchAndCompare(query, List.of(task, task2)); - searchAndCompareAsList(queryMore, List.of(task, task2)); + searchAndCompare(query, task); + searchAndCompareAsList(queryMore, List.of(task)); + + // in list + String queryInList = String.format("tasks: transitionId in ('%s', '%s')", TEST_TRANSITION_ID, TEST_TRANSITION2_ID); + + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId in ('%s' : '%s']", TEST_TRANSITION_ID, TEST_TRANSITION2_ID); + + searchAndCompareAsList(queryInRange, List.of(task2)); // sort String querySort = String.format("tasks: caseId eq '%s' sort by transitionId", case1.getStringId()); @@ -190,6 +206,16 @@ public void testSearchByTitle() { searchAndCompare(query, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and title in ('%s', '%s')", TEST_TRANSITION_ID, task.getTitle().getDefaultValue(), task3.getTitle().getDefaultValue()); + + searchAndCompareAsList(queryInList, List.of(task, task2, task3)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and title in ('%s' : '%s']", TEST_TRANSITION_ID, task.getTitle().getDefaultValue(), task3.getTitle().getDefaultValue()); + + searchAndCompareAsList(queryInRange, List.of(task3)); + // sort String querySort = String.format("tasks: transitionId eq '%s' sort by title", TEST_TRANSITION_ID); String querySort2 = String.format("tasks: transitionId eq '%s' sort by title desc", TEST_TRANSITION_ID); @@ -265,6 +291,11 @@ public void testSearchByUserId() { searchAndCompare(queryOther, List.of(task3)); searchAndCompareAsList(queryMore, List.of(task, task2)); + // in list + String queryInList = String.format("tasks: userId in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(task, task2, task3)); + // sort String querySort = String.format("tasks: transitionId eq '%s' sort by userId", TEST_TRANSITION_ID); String querySort2 = String.format("tasks: transitionId eq '%s' sort by userId desc", TEST_TRANSITION_ID); @@ -295,6 +326,14 @@ public void testSearchByCaseId() { searchAndCompare(queryOther, case2Tasks); searchAndCompareAsList(queryMore, case1Tasks); + // in list + String queryInList = String.format("tasks: caseId in ('%s', '%s')", case1.getStringId(), case2.getStringId()); + + List<Task> allTasks = new ArrayList<>(); + allTasks.addAll(case1Tasks); + allTasks.addAll(case2Tasks); + searchAndCompareAsList(queryInList, allTasks); + // sort String querySort = String.format("tasks: processId eq '%s' sort by caseId", net.getStringId()); String querySort2 = String.format("tasks: processId eq '%s' sort by caseId desc", net.getStringId()); @@ -333,9 +372,17 @@ public void testSearchByProcessId() { searchAndCompare(queryOther, net2Tasks); searchAndCompareAsList(queryMore, netTasks); + // in list + String queryInList = String.format("tasks: processId in ('%s', '%s')", net.getStringId(), net2.getStringId()); + + List<Task> allTasks = new ArrayList<>(); + allTasks.addAll(netTasks); + allTasks.addAll(net2Tasks); + searchAndCompareAsList(queryInList, allTasks); + // sort - String querySort = String.format("tasks: processId eq '%s' or processId eq '%s' sort by processId", net.getStringId(), net2.getStringId()); - String querySort2 = String.format("tasks: processId eq '%s' or processId eq '%s' sort by processId desc", net.getStringId(), net2.getStringId()); + String querySort = String.format("tasks: processId in ('%s', '%s') sort by processId", net.getStringId(), net2.getStringId()); + String querySort2 = String.format("tasks: processId in ('%s', '%s') sort by processId desc", net.getStringId(), net2.getStringId()); List<Task> asc = new ArrayList<>(); asc.addAll(netTasks); @@ -370,6 +417,14 @@ public void testSearchByLastAssign() { searchAndCompare(queryBefore, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and lastAssign in (%s, %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastAssigned()), toDateTimeString(task2.getLastAssigned())); + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and lastAssign in [%s : %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastAssigned()), toDateTimeString(task2.getLastAssigned())); + searchAndCompareAsList(queryInRange, List.of(task)); + // sort String querySort = String.format("tasks: transitionId eq '%s' sort by lastAssign", TEST_TRANSITION_ID); String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastAssign desc", TEST_TRANSITION_ID); @@ -402,6 +457,14 @@ public void testSearchByLastFinish() { searchAndCompare(queryBefore, List.of(task, task2)); searchAndCompareAsList(queryMore, List.of(task, task2)); + // in list + String queryInList = String.format("tasks: transitionId eq '%s' and lastFinish in (%s, %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastFinished()), toDateTimeString(task2.getLastFinished())); + searchAndCompareAsList(queryInList, List.of(task, task2)); + + // in range + String queryInRange = String.format("tasks: transitionId eq '%s' and lastFinish in [%s : %s)", TEST_TRANSITION_ID, toDateTimeString(task.getLastFinished()), toDateTimeString(task2.getLastFinished())); + searchAndCompareAsList(queryInRange, List.of(task)); + // sort String querySort = String.format("tasks: transitionId eq '%s' sort by lastFinish", TEST_TRANSITION_ID); String querySort2 = String.format("tasks: transitionId eq '%s' sort by lastFinish desc", TEST_TRANSITION_ID); @@ -409,4 +472,33 @@ public void testSearchByLastFinish() { searchAndCompareAsListInOrder(querySort, List.of(task, task2)); searchAndCompareAsListInOrder(querySort2, List.of(task2, task)); } + + @Test + public void testPagination() { + PetriNet net = importPetriNet("search/search_test.xml"); + List<Task> tasks = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + Case c = importHelper.createCase("Search Test", net); + tasks.addAll(c.getTasks().values().stream() + .map(taskPair -> taskService.findOne(taskPair.getTaskStringId())) + .sorted(Comparator.comparing(Task::getStringId)) + .collect(Collectors.toList())); + } + + String queryOne = String.format("task: processId eq '%s'", net.getStringId()); + String queryMore = String.format("tasks: processId eq '%s'", net.getStringId()); + String queryMoreCustomPagination = String.format("tasks: processId eq '%s' page 1 size 5", net.getStringId()); + + long count = searchService.count(queryOne); + assert count == 30; + + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, Task.class), tasks.get(0), Task::getStringId); + + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, Task.class), tasks.subList(0, 19), Task::getStringId); + + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, Task.class), tasks.subList(5, 9), Task::getStringId); + } } diff --git a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java index a8e9dcdf27d..25d1ab86bd5 100644 --- a/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java +++ b/src/test/java/com/netgrif/application/engine/search/SearchUserTest.java @@ -2,7 +2,6 @@ import com.netgrif.application.engine.TestHelper; import com.netgrif.application.engine.auth.domain.Authority; -import com.netgrif.application.engine.auth.domain.IUser; import com.netgrif.application.engine.auth.domain.User; import com.netgrif.application.engine.petrinet.domain.roles.ProcessRole; import com.netgrif.application.engine.search.interfaces.ISearchService; @@ -16,9 +15,11 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.junit.jupiter.SpringExtension; +import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; + +import static com.netgrif.application.engine.search.utils.SearchTestUtils.*; @Slf4j @SpringBootTest @@ -43,100 +44,166 @@ void setup() { auths = importHelper.createAuthorities(Map.of("user", Authority.user, "admin", Authority.admin)); } - private IUser createUser(String name, String surname, String email, String authority) { + private User createUser(String name, String surname, String email, String authority) { User user = new User(email, "password", name, surname); Authority[] authorities = new Authority[]{auths.get(authority)}; ProcessRole[] processRoles = new ProcessRole[]{}; - return importHelper.createUser(user, authorities, processRoles); - } - - private static IUser convertToIUser(Object userObject) { - assert userObject instanceof IUser; - return (IUser) userObject; + return (User) importHelper.createUser(user, authorities, processRoles); } - private static List<IUser> convertToIUserList(Object userListObject) { - assert userListObject instanceof List<?>; - for (Object userObject : (List<?>) userListObject) { - assert userObject instanceof IUser; - } + private void searchAndCompare(String query, User expected) { + long count = searchService.count(query); + assert count == 1; - return (List<IUser>) userListObject; + Object actual = searchService.search(query); + compareById(convertToObject(actual, User.class), expected, User::getStringId); } - private void compareUsers(IUser actual, IUser expected) { - assert actual.getStringId().equals(expected.getStringId()); + private void searchAndCompare(String query, List<User> expected) { + long count = searchService.count(query); + assert count == expected.size(); + + Object actual = searchService.search(query); + compareById(convertToObject(actual, User.class), expected, User::getStringId); } - private void compareUsers(IUser actual, List<IUser> expected) { - List<String> expectedStringIds = expected.stream().map(IUser::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsList(String query, List<User> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert expectedStringIds.contains(actual.getStringId()); + Object actual = searchService.search(query); + compareById(convertToObjectList(actual, User.class), expected, User::getStringId); } - private void compareUsers(List<IUser> actual, List<IUser> expected) { - List<String> actualStringIds = actual.stream().map(IUser::getStringId).collect(Collectors.toList()); - List<String> expectedStringIds = expected.stream().map(IUser::getStringId).collect(Collectors.toList()); + private void searchAndCompareAsListInOrder(String query, List<User> expected) { + long count = searchService.count(query); + assert count == expected.size(); - assert actualStringIds.containsAll(expectedStringIds); + Object actual = searchService.search(query); + compareByIdInOrder(convertToObjectList(actual, User.class), expected, User::getStringId); } @Test public void testSearchById() { - IUser user1 = createUser("name1", "surname1", "email1", "user"); - IUser user2 = createUser("name2", "surname2", "email2", "admin"); + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); String query = String.format("user: id eq '%s'", user1.getStringId()); - long count = searchService.count(query); - assert count == 1; + searchAndCompare(query, user1); + + // in list + String queryInList = String.format("users: id in ('%s', '%s')", user1.getStringId(), user2.getStringId()); + + searchAndCompareAsList(queryInList, List.of(user1, user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s') sort by id", user1.getStringId(), user2.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s') sort by id desc", user1.getStringId(), user2.getStringId()); - Object foundUser = searchService.search(query); - compareUsers(convertToIUser(foundUser), user1); + searchAndCompareAsListInOrder(querySort, List.of(user1, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1)); } @Test public void testSearchByEmail() { - IUser user1 = createUser("name1", "surname1", "email1", "user"); - IUser user2 = createUser("name2", "surname2", "email2", "admin"); + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); String query = String.format("user: email eq '%s'", user1.getEmail()); - long count = searchService.count(query); - assert count == 1; + searchAndCompare(query, user1); + + // in list + String queryInList = String.format("users: email in ('%s', '%s')", user1.getEmail(), user2.getEmail()); + + searchAndCompareAsList(queryInList, List.of(user1, user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s') sort by email", user1.getStringId(), user2.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s') sort by email desc", user1.getStringId(), user2.getStringId()); - Object foundUser = searchService.search(query); - compareUsers(convertToIUser(foundUser), user1); + searchAndCompareAsListInOrder(querySort, List.of(user1, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1)); } @Test public void testSearchByName() { - IUser user1 = createUser("name1", "surname1", "email1", "user"); - IUser user2 = createUser("name2", "surname2", "email2", "admin"); - IUser user3 = createUser("name1", "surname1", "email3", "user"); + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + User user3 = createUser("name1", "surname1", "email3", "user"); String query = String.format("users: name eq '%s'", user1.getName()); - long count = searchService.count(query); - assert count == 2; + searchAndCompareAsList(query, List.of(user1, user3)); + + // in list + String queryInList = String.format("users: name in ('%s', '%s')", user1.getName(), user2.getName()); + + searchAndCompareAsList(queryInList, List.of(user1, user2, user3)); + + // in range + String queryInRange = String.format("users: name in ('%s' : '%s']", user1.getName(), user2.getName()); + + searchAndCompareAsList(queryInRange, List.of(user2)); - Object foundUsers = searchService.search(query); - compareUsers(convertToIUserList(foundUsers), List.of(user1, user3)); + // sort + String querySort = String.format("users: id in ('%s', '%s', '%s') sort by name", user1.getStringId(), user2.getStringId(), user3.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s', '%s') sort by name desc", user1.getStringId(), user2.getStringId(), user3.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user3, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1, user3)); } @Test public void testSearchBySurname() { - IUser user1 = createUser("name1", "surname1", "email1", "user"); - IUser user2 = createUser("name2", "surname2", "email2", "admin"); - IUser user3 = createUser("name1", "surname1", "email3", "user"); + User user1 = createUser("name1", "surname1", "email1", "user"); + User user2 = createUser("name2", "surname2", "email2", "admin"); + User user3 = createUser("name1", "surname1", "email3", "user"); String query = String.format("users: surname eq '%s'", user1.getSurname()); - long count = searchService.count(query); - assert count == 2; + searchAndCompareAsList(query, List.of(user1, user3)); + + // in list + String queryInList = String.format("users: surname in ('%s', '%s')", user1.getSurname(), user2.getSurname()); + + searchAndCompareAsList(queryInList, List.of(user1, user2, user3)); + + // in range + String queryInRange = String.format("users: surname in ('%s' : '%s']", user1.getSurname(), user2.getSurname()); - Object foundUsers = searchService.search(query); - compareUsers(convertToIUserList(foundUsers), List.of(user1, user3)); + searchAndCompareAsList(queryInRange, List.of(user2)); + + // sort + String querySort = String.format("users: id in ('%s', '%s', '%s') sort by surname", user1.getStringId(), user2.getStringId(), user3.getStringId()); + String querySort2 = String.format("users: id in ('%s', '%s', '%s') sort by surname desc", user1.getStringId(), user2.getStringId(), user3.getStringId()); + + searchAndCompareAsListInOrder(querySort, List.of(user1, user3, user2)); + searchAndCompareAsListInOrder(querySort2, List.of(user2, user1, user3)); } + @Test + public void testPagination() { + List<User> users = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + users.add(createUser("name" + i, "surname" + i , "email" + i, "user")); + } + + String queryOne = String.format("user: email contains '%s'", "email"); + String queryMore = String.format("users: email contains '%s'", "email"); + String queryMoreCustomPagination = String.format("users: email contains '%s' page 1 size 5", "email"); + + long count = searchService.count(queryOne); + assert count == 50; + + Object actual = searchService.search(queryOne); + compareById(convertToObject(actual, User.class), users.get(0), User::getStringId); + + actual = searchService.search(queryMore); + compareById(convertToObjectList(actual, User.class), users.subList(0, 19), User::getStringId); + + actual = searchService.search(queryMoreCustomPagination); + compareById(convertToObjectList(actual, User.class), users.subList(5, 9), User::getStringId); + } }