Repository: kestra-io/kestra (java)

Issue:
[Execution filter] can't filter executions having multiple labels
### Describe the issue

Prior Kestra v0.20 users are able to filer executions having more than one label - e.g. only executions having labels `foo:bar AND baz:buz`.

The v0.20.23 version seems to have removed the ability to specify more than one label.

The current v0.23-SNAPSHOT version seems to allow to speicfy more than one label, although the semantic has completely changed. The query `labels is foo:bar; labels is bar:buz` gets interpreted as  `foo:bar OR baz:buz`, which is a departure from the pre-v0.20 behavior.

This is a notable regression from the pre-v0.20's behavior.

### Environment

- Kestra Version: develop


Pull request:
fix(filters): change label filtering to 'and' instead of 'or'
close #8489


Patch:
diff --git a/build.gradle b/build.gradle
index 7bac50a40..9f727bfe2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -165,7 +165,7 @@ allprojects {
  * Test
  **********************************************************************************************************************/
 subprojects {
-    if (it.name != 'platform') {
+    if (it.name != 'platform' && it.name != 'jmh-benchmarks') {
         apply plugin: "com.adarshr.test-logger"
 
         java {
@@ -268,7 +268,7 @@ subprojects {
  * Allure Reports
  **********************************************************************************************************************/
 subprojects {
-    if (it.name != 'platform') {
+    if (it.name != 'platform' && it.name != 'jmh-benchmarks') {
         dependencies {
             testImplementation platform("io.qameta.allure:allure-bom")
             testImplementation "io.qameta.allure:allure-junit5"
@@ -295,7 +295,7 @@ subprojects {
  * Jacoco
  **********************************************************************************************************************/
 subprojects {
-    if (it.name != 'platform') {
+    if (it.name != 'platform' && it.name != 'jmh-benchmarks') {
         apply plugin: 'jacoco'
 
         test {
@@ -496,98 +496,101 @@ nexusPublishing {
 }
 
 subprojects {
-    apply plugin: "maven-publish"
-    apply plugin: 'signing'
-    apply plugin: 'ru.vyarus.pom'
-    apply plugin: 'ru.vyarus.github-info'
-
-    javadoc {
-        options {
-            locale = 'en_US'
-            encoding = 'UTF-8'
-            addStringOption("Xdoclint:none", "-quiet")
+
+    if (it.name != 'jmh-benchmarks') {
+        apply plugin: "maven-publish"
+        apply plugin: 'signing'
+        apply plugin: 'ru.vyarus.pom'
+        apply plugin: 'ru.vyarus.github-info'
+
+        javadoc {
+            options {
+                locale = 'en_US'
+                encoding = 'UTF-8'
+                addStringOption("Xdoclint:none", "-quiet")
+            }
         }
-    }
 
-    tasks.register('sourcesJar', Jar) {
-        dependsOn = [':core:copyGradleProperties']
-        dependsOn = [':ui:assembleFrontend']
-        archiveClassifier.set('sources')
-        from sourceSets.main.allSource
-    }
-    sourcesJar.dependsOn ':core:copyGradleProperties'
-    sourcesJar.dependsOn ':ui:assembleFrontend'
+        tasks.register('sourcesJar', Jar) {
+            dependsOn = [':core:copyGradleProperties']
+            dependsOn = [':ui:assembleFrontend']
+            archiveClassifier.set('sources')
+            from sourceSets.main.allSource
+        }
+        sourcesJar.dependsOn ':core:copyGradleProperties'
+        sourcesJar.dependsOn ':ui:assembleFrontend'
 
-    tasks.register('javadocJar', Jar) {
-        archiveClassifier.set('javadoc')
-        from javadoc
-    }
+        tasks.register('javadocJar', Jar) {
+            archiveClassifier.set('javadoc')
+            from javadoc
+        }
 
-    tasks.register('testsJar', Jar) {
-        group = 'build'
-        description = 'Build the tests jar'
+        tasks.register('testsJar', Jar) {
+            group = 'build'
+            description = 'Build the tests jar'
 
-        archiveClassifier.set('tests')
-        if (sourceSets.matching { it.name == 'test'}) {
-            from sourceSets.named('test').get().output
+            archiveClassifier.set('tests')
+            if (sourceSets.matching { it.name == 'test'}) {
+                from sourceSets.named('test').get().output
+            }
         }
-    }
 
-    github {
-        user 'kestra-io'
-        license 'Apache'
-        repository 'kestra'
-        site 'https://kestra.io'
-    }
+        github {
+            user 'kestra-io'
+            license 'Apache'
+            repository 'kestra'
+            site 'https://kestra.io'
+        }
 
-    maven.pom {
-        description = 'The modern, scalable orchestrator & scheduler open source platform'
+        maven.pom {
+            description = 'The modern, scalable orchestrator & scheduler open source platform'
 
-        developers {
-            developer {
-                id = "tchiotludo"
-                name = "Ludovic Dehon"
+            developers {
+                developer {
+                    id = "tchiotludo"
+                    name = "Ludovic Dehon"
+                }
             }
         }
-    }
 
-    publishing {
-        publications {
-            sonatypePublication(MavenPublication) {
-                version project.version
-
-                if (project.name.contains('cli')) {
-                    groupId "io.kestra"
-                    artifactId "kestra"
-
-                    artifact shadowJar
-                    artifact executableJar
-                } else if (project.name.contains('platform')){
-                    groupId project.group
-                    artifactId project.name
-                } else {
-                    from components.java
-
-                    groupId project.group
-                    artifactId project.name
-
-                    artifact sourcesJar
-                    artifact javadocJar
-                    artifact testsJar
+        publishing {
+            publications {
+                sonatypePublication(MavenPublication) {
+                    version project.version
+
+                    if (project.name.contains('cli')) {
+                        groupId "io.kestra"
+                        artifactId "kestra"
+
+                        artifact shadowJar
+                        artifact executableJar
+                    } else if (project.name.contains('platform')){
+                        groupId project.group
+                        artifactId project.name
+                    } else {
+                        from components.java
+
+                        groupId project.group
+                        artifactId project.name
+
+                        artifact sourcesJar
+                        artifact javadocJar
+                        artifact testsJar
+                    }
                 }
             }
         }
-    }
 
-    signing {
-        // only sign JARs that we publish to Sonatype
-        required { gradle.taskGraph.hasTask("publishSonatypePublicationPublicationToSonatypeRepository") }
-        sign publishing.publications.sonatypePublication
-    }
+        signing {
+            // only sign JARs that we publish to Sonatype
+            required { gradle.taskGraph.hasTask("publishSonatypePublicationPublicationToSonatypeRepository") }
+            sign publishing.publications.sonatypePublication
+        }
 
-    tasks.withType(GenerateModuleMetadata).configureEach {
-        // Suppression this validation error as we want to enforce the Kestra platform
-        suppressedValidationErrors.add('enforced-platform')
+        tasks.withType(GenerateModuleMetadata).configureEach {
+            // Suppression this validation error as we want to enforce the Kestra platform
+            suppressedValidationErrors.add('enforced-platform')
+        }
     }
 }
 
diff --git a/jdbc-h2/src/main/java/io/kestra/repository/h2/H2ExecutionRepositoryService.java b/jdbc-h2/src/main/java/io/kestra/repository/h2/H2ExecutionRepositoryService.java
index 77586d5d0..5b87667d1 100644
--- a/jdbc-h2/src/main/java/io/kestra/repository/h2/H2ExecutionRepositoryService.java
+++ b/jdbc-h2/src/main/java/io/kestra/repository/h2/H2ExecutionRepositoryService.java
@@ -47,6 +47,6 @@ public abstract class H2ExecutionRepositoryService {
             });
 
 
-        return conditions.isEmpty() ? DSL.trueCondition() : DSL.or(conditions);
+        return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
     }
 }
diff --git a/jdbc-mysql/src/main/java/io/kestra/repository/mysql/MysqlExecutionRepositoryService.java b/jdbc-mysql/src/main/java/io/kestra/repository/mysql/MysqlExecutionRepositoryService.java
index e291d3c1d..47c400628 100644
--- a/jdbc-mysql/src/main/java/io/kestra/repository/mysql/MysqlExecutionRepositoryService.java
+++ b/jdbc-mysql/src/main/java/io/kestra/repository/mysql/MysqlExecutionRepositoryService.java
@@ -44,7 +44,7 @@ public abstract class MysqlExecutionRepositoryService {
 
             });
 
-        return conditions.isEmpty() ? DSL.trueCondition() : DSL.or(conditions);
+        return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
     }
 
 }
diff --git a/jdbc-postgres/src/main/java/io/kestra/repository/postgres/PostgresExecutionRepositoryService.java b/jdbc-postgres/src/main/java/io/kestra/repository/postgres/PostgresExecutionRepositoryService.java
index f04e938e4..ac4c37c27 100644
--- a/jdbc-postgres/src/main/java/io/kestra/repository/postgres/PostgresExecutionRepositoryService.java
+++ b/jdbc-postgres/src/main/java/io/kestra/repository/postgres/PostgresExecutionRepositoryService.java
@@ -4,7 +4,6 @@ import io.kestra.core.models.QueryFilter;
 import io.kestra.core.models.executions.Execution;
 import io.kestra.jdbc.AbstractJdbcRepository;
 import org.jooq.Condition;
-import org.jooq.Field;
 import org.jooq.impl.DSL;
 
 import java.util.ArrayList;
@@ -44,7 +43,7 @@ public abstract class PostgresExecutionRepositoryService {
 
             });
 
-        return conditions.isEmpty() ? DSL.trueCondition() : DSL.or(conditions);
+        return conditions.isEmpty() ? DSL.trueCondition() : DSL.and(conditions);
     }
 
 }


Test patch:
diff --git a/core/src/test/java/io/kestra/core/repositories/AbstractExecutionRepositoryTest.java b/core/src/test/java/io/kestra/core/repositories/AbstractExecutionRepositoryTest.java
index ba814e376..654f9e1f7 100644
--- a/core/src/test/java/io/kestra/core/repositories/AbstractExecutionRepositoryTest.java
+++ b/core/src/test/java/io/kestra/core/repositories/AbstractExecutionRepositoryTest.java
@@ -191,7 +191,7 @@ public abstract class AbstractExecutionRepositoryTest {
                 .build()
         );
         executions = executionRepository.find(Pageable.from(1, 10),  MAIN_TENANT, filters);
-        assertThat(executions.getTotal()).isEqualTo(1L);
+        assertThat(executions.getTotal()).isEqualTo(0L);
 
         filters = List.of(QueryFilter.builder()
             .field(QueryFilter.Field.FLOW_ID)
@@ -816,4 +816,26 @@ public abstract class AbstractExecutionRepositoryTest {
         List<Execution> executions = executionRepository.findAllAsync(MAIN_TENANT).collectList().block();
         assertThat(executions).hasSize(29); // used by the backup so it contains TEST executions
     }
+
+    @Test
+    protected void shouldFindByLabel() {
+        inject();
+
+        List<QueryFilter> filters = List.of(QueryFilter.builder()
+            .field(QueryFilter.Field.LABELS)
+            .operation(QueryFilter.Op.EQUALS)
+            .value(Map.of("key", "value"))
+            .build());
+        List<Execution> executions = executionRepository.find(Pageable.from(1, 10),  MAIN_TENANT, filters);
+        assertThat(executions.size()).isEqualTo(1L);
+
+        // Filtering by two pairs of labels, since now its a and behavior, it should not return anything
+        filters = List.of(QueryFilter.builder()
+            .field(QueryFilter.Field.LABELS)
+            .operation(QueryFilter.Op.EQUALS)
+            .value(Map.of("key", "value", "keyother", "valueother"))
+            .build());
+        executions = executionRepository.find(Pageable.from(1, 10),  MAIN_TENANT, filters);
+        assertThat(executions.size()).isEqualTo(0L);
+    }
 }