@@ -700,4 +700,164 @@ public Void execute(CommandContext commandContext) {
700700 });
701701 }
702702
703+ @ Test
704+ @ Deployment (resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml" )
705+ public void testOrQuery () {
706+ ProcessInstance processInstance1 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
707+ ProcessInstance processInstance2 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
708+
709+ ExternalWorkerJob orderJob1 = managementService .createExternalWorkerJobQuery ()
710+ .processInstanceId (processInstance1 .getId ())
711+ .elementId ("externalOrder" )
712+ .singleResult ();
713+ ExternalWorkerJob customerJob1 = managementService .createExternalWorkerJobQuery ()
714+ .processInstanceId (processInstance1 .getId ())
715+ .elementId ("externalCustomer1" )
716+ .singleResult ();
717+ ExternalWorkerJob orderJob2 = managementService .createExternalWorkerJobQuery ()
718+ .processInstanceId (processInstance2 .getId ())
719+ .elementId ("externalOrder" )
720+ .singleResult ();
721+ assertThat (orderJob1 ).isNotNull ();
722+ assertThat (customerJob1 ).isNotNull ();
723+ assertThat (orderJob2 ).isNotNull ();
724+
725+ // OR query: match by jobId(orderJob1) OR processInstanceId(processInstance2)
726+ List <ExternalWorkerJob > jobs = managementService .createExternalWorkerJobQuery ()
727+ .or ()
728+ .jobId (orderJob1 .getId ())
729+ .processInstanceId (processInstance2 .getId ())
730+ .endOr ()
731+ .list ();
732+ // orderJob1 + both jobs from processInstance2
733+ assertThat (jobs ).hasSize (3 );
734+ assertThat (jobs ).extracting (ExternalWorkerJob ::getId )
735+ .contains (orderJob1 .getId (), orderJob2 .getId ());
736+
737+ // OR query: match by specific job IDs using jobId OR elementId scoped to a process
738+ jobs = managementService .createExternalWorkerJobQuery ()
739+ .processInstanceId (processInstance1 .getId ())
740+ .or ()
741+ .jobId (orderJob1 .getId ())
742+ .elementId ("externalCustomer1" )
743+ .endOr ()
744+ .list ();
745+ assertThat (jobs ).hasSize (2 );
746+ assertThat (jobs ).extracting (ExternalWorkerJob ::getId )
747+ .containsExactlyInAnyOrder (orderJob1 .getId (), customerJob1 .getId ());
748+
749+ // OR with no match
750+ assertThat (managementService .createExternalWorkerJobQuery ()
751+ .or ()
752+ .processInstanceId ("nonexistent" )
753+ .executionId ("nonexistent" )
754+ .endOr ()
755+ .count ()).isZero ();
756+ }
757+
758+ @ Test
759+ @ Deployment (resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml" )
760+ public void testOrWithAndQuery () {
761+ ProcessInstance processInstance1 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
762+ ProcessInstance processInstance2 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
763+
764+ // AND (processInstanceId) + OR (elementId OR elementId)
765+ List <ExternalWorkerJob > jobs = managementService .createExternalWorkerJobQuery ()
766+ .processInstanceId (processInstance1 .getId ())
767+ .or ()
768+ .elementId ("externalOrder" )
769+ .elementId ("externalCustomer1" )
770+ .endOr ()
771+ .list ();
772+ // In OR the last setter wins, so only externalCustomer1 matches within OR scope
773+ // Combined with AND processInstanceId, returns 1 job
774+ assertThat (jobs ).hasSize (1 );
775+ assertThat (jobs .get (0 ).getElementId ()).isEqualTo ("externalCustomer1" );
776+
777+ // Test with different OR fields
778+ ExternalWorkerJob orderJob1 = managementService .createExternalWorkerJobQuery ()
779+ .processInstanceId (processInstance1 .getId ())
780+ .elementId ("externalOrder" )
781+ .singleResult ();
782+
783+ jobs = managementService .createExternalWorkerJobQuery ()
784+ .processInstanceId (processInstance1 .getId ())
785+ .or ()
786+ .jobId (orderJob1 .getId ())
787+ .elementId ("externalCustomer1" )
788+ .endOr ()
789+ .list ();
790+ assertThat (jobs ).hasSize (2 );
791+
792+ // AND with non-matching processInstanceId + OR should return empty
793+ assertThat (managementService .createExternalWorkerJobQuery ()
794+ .processInstanceId ("nonexistent" )
795+ .or ()
796+ .elementId ("externalOrder" )
797+ .elementId ("externalCustomer1" )
798+ .endOr ()
799+ .count ()).isZero ();
800+ }
801+
802+ @ Test
803+ @ Deployment (resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml" )
804+ public void testOrQueryErrors () {
805+ runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
806+
807+ assertThatThrownBy (() -> managementService .createExternalWorkerJobQuery ().or ().or ())
808+ .isInstanceOf (FlowableException .class )
809+ .hasMessageContaining ("the query is already in an or statement" );
810+
811+ assertThatThrownBy (() -> managementService .createExternalWorkerJobQuery ().endOr ())
812+ .isInstanceOf (FlowableException .class )
813+ .hasMessageContaining ("endOr() can only be called after calling or()" );
814+ }
815+
816+ @ Test
817+ @ Deployment (resources = "org/flowable/engine/test/api/mgmt/ExternalWorkerJobQueryTest.bpmn20.xml" )
818+ public void testConsecutiveOrQuery () {
819+ ProcessInstance pi1 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
820+ ProcessInstance pi2 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
821+ ProcessInstance pi3 = runtimeService .startProcessInstanceByKey ("externalWorkerJobQueryTest" );
822+
823+ ExternalWorkerJob job1Order = managementService .createExternalWorkerJobQuery ()
824+ .processInstanceId (pi1 .getId ()).elementId ("externalOrder" ).singleResult ();
825+ ExternalWorkerJob job2Order = managementService .createExternalWorkerJobQuery ()
826+ .processInstanceId (pi2 .getId ()).elementId ("externalOrder" ).singleResult ();
827+
828+ // First OR group matches {job1Order, all pi2 jobs}, second matches {all pi2 jobs, all pi3 jobs}
829+ // Intersection = pi2's jobs (2 jobs)
830+ List <ExternalWorkerJob > jobs = managementService .createExternalWorkerJobQuery ()
831+ .or ()
832+ .jobId (job1Order .getId ())
833+ .processInstanceId (pi2 .getId ())
834+ .endOr ()
835+ .or ()
836+ .processInstanceId (pi2 .getId ())
837+ .processInstanceId (pi3 .getId ())
838+ .endOr ()
839+ .list ();
840+ // Second OR group: last setter wins -> processInstanceId=pi3. So second group matches pi3 only.
841+ // Intersection of {job1Order, pi2 jobs} AND {pi3 jobs} = empty
842+ assertThat (jobs ).hasSize (0 );
843+
844+ // Let's use different fields instead:
845+ jobs = managementService .createExternalWorkerJobQuery ()
846+ .or ()
847+ .jobId (job1Order .getId ())
848+ .processInstanceId (pi2 .getId ())
849+ .endOr ()
850+ .or ()
851+ .jobId (job2Order .getId ())
852+ .processInstanceId (pi1 .getId ())
853+ .endOr ()
854+ .list ();
855+ // First group: {job1Order} union {pi2's 2 jobs} = {job1Order, job2Order, job2Customer}
856+ // Second group: {job2Order} union {pi1's 2 jobs} = {job2Order, job1Order, job1Customer}
857+ // Intersection: {job1Order, job2Order} - both appear in both groups
858+ assertThat (jobs ).hasSize (2 );
859+ assertThat (jobs ).extracting (ExternalWorkerJob ::getId )
860+ .containsExactlyInAnyOrder (job1Order .getId (), job2Order .getId ());
861+ }
862+
703863}
0 commit comments