diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsqllegacy/JpaQueryRsqlVisitor.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsqllegacy/JpaQueryRsqlVisitor.java index 642943485..ca5ba276c 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsqllegacy/JpaQueryRsqlVisitor.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rsqllegacy/JpaQueryRsqlVisitor.java @@ -563,7 +563,11 @@ public class JpaQueryRsqlVisitor & RsqlQueryField, T> extends } private Expression caseWise(final CriteriaBuilder cb, final Expression expression) { - return ensureIgnoreCase ? cb.upper(expression) : expression; + if (expression instanceof Path path && path.getJavaType() != String.class) { + return expression; // non string values doesn't support uppercase + } else { + return ensureIgnoreCase ? cb.upper(expression) : expression; + } } private String caseWise(final String str) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java index 50579c524..e78b43f92 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/RSQLToSQLTest.java @@ -47,6 +47,7 @@ class RSQLToSQLTest { @Test void printPG() { + printFrom(JpaTarget.class, TargetFields.class, "metadata.x==y"); printFrom(JpaTarget.class, TargetFields.class, "tag!=TAG1 and tag==TAG2"); printFrom(JpaTarget.class, TargetFields.class, "tag==TAG1 and tag!=TAG2"); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderLegacyTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderLegacyTest.java new file mode 100644 index 000000000..e8019810c --- /dev/null +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderLegacyTest.java @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.repository.jpa.rsql.sa; + +import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1; +import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2; + +import java.util.Arrays; +import java.util.List; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.hawkbit.repository.RsqlQueryField; +import org.eclipse.hawkbit.repository.jpa.rsqllegacy.SpecificationBuilderLegacy; +import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder; +import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder; +import org.junit.jupiter.api.Test; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.orm.jpa.vendor.Database; + +@SuppressWarnings("java:S2699") // java:S2699 - assertions are un the super methods that are called +@Slf4j +class SpecificationBuilderLegacyTest extends SpecificationBuilderTest { + + private final SpecificationBuilderLegacy builder = new SpecificationBuilderLegacy<>(RootField.class, null, Database.H2); + + private static void runWithRsqlToSpecBuilder(final Runnable runnable, final RsqlToSpecBuilder rsqlToSpecBuilder) { + final RsqlToSpecBuilder defaultBuilder = RsqlConfigHolder.getInstance().getRsqlToSpecBuilder(); + RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder); + try { + runnable.run(); + } finally { + RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(defaultBuilder); + } + } + + @Test + void singularStringAttributeG1() { + runWithRsqlToSpecBuilder(super::singularStringAttribute, LEGACY_G1); + } + @Override + @Test + void singularStringAttribute() { + runWithRsqlToSpecBuilder(super::singularStringAttribute, LEGACY_G2); + } + + @Test + void singularIntAttributeG1() { + runWithRsqlToSpecBuilder(super::singularIntAttribute, LEGACY_G1); + } + @Override + @Test + void singularIntAttribute() { + runWithRsqlToSpecBuilder(super::singularIntAttribute, LEGACY_G2); + } + + @Test + void singularEntityAttributeG1() { + runWithRsqlToSpecBuilder(super::singularEntityAttribute, LEGACY_G1); + } + @Override + @Test + void singularEntityAttribute() { + runWithRsqlToSpecBuilder(super::singularEntityAttribute, LEGACY_G2); + } + + @Test + void pluralSubSetAttributeG1() { + runWithRsqlToSpecBuilder(super::pluralSubSetAttribute, LEGACY_G1); + } + @Override + @Test + void pluralSubSetAttribute() { + runWithRsqlToSpecBuilder(super::pluralSubSetAttribute, LEGACY_G2); + } + + // Legacy G1 doesn't support hibernate maps + @Override + @Test + void pluralSubMapAttribute() { + runWithRsqlToSpecBuilder(super::pluralSubMapAttribute, LEGACY_G2); + } + + @Override + protected Specification getSpecification(final String rsql) { + return builder.specification(rsql); + } + + @Getter + private enum RootField implements RsqlQueryField { + + INTVALUE("intValue"), + STRVALUE("strValue"), + SUBENTITY("subEntity", "strValue", "intValue"), + SUBSET("subSet", "strValue", "intValue"), + SUBMAP("subMap"); + + private final String jpaEntityFieldName; + private final List subEntityAttributes; + + RootField(final String jpaEntityFieldName, final String... subFields) { + this.jpaEntityFieldName = jpaEntityFieldName; + this.subEntityAttributes = Arrays.asList(subFields); + } + + @Override + public boolean isMap() { + return this == SUBMAP; + } + } +} \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderTest.java index 9a7fc9634..3ef4175a6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/sa/SpecificationBuilderTest.java @@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.rsql.sa; import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.G3; +import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1; import java.util.List; import java.util.Map; @@ -23,24 +24,24 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.hawkbit.repository.jpa.rsql.RSQLToSQL; import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser; import org.eclipse.hawkbit.repository.jpa.rsql.SpecificationBuilder; +import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.jpa.domain.Specification; import org.springframework.orm.jpa.vendor.Database; @SuppressWarnings("java:S5961") // complex check because the matter is very complex @DataJpaTest(properties = { "logging.level.org.eclipse.hawkbit.repository.jpa.rsql=DEBUG" -}, excludeAutoConfiguration = { FlywayAutoConfiguration.class } ) +}, excludeAutoConfiguration = { FlywayAutoConfiguration.class }) @EnableAutoConfiguration @Slf4j class SpecificationBuilderTest { - private static final Database DATABASE = Database.H2; - @Autowired private SubRepository subRepository; @Autowired @@ -48,191 +49,326 @@ class SpecificationBuilderTest { @Autowired private EntityManager entityManager; - private final SpecificationBuilder builder = new SpecificationBuilder<>(null, false, DATABASE); + private final SpecificationBuilder builder = new SpecificationBuilder<>(null, false, Database.H2); @Test void singularStringAttribute() { - final Root root = rootRepository.save(new Root().setStrValue("rootX")); - final Root root2 = rootRepository.save(new Root().setStrValue("rootX")); - final Root root3 = rootRepository.save(new Root().setStrValue("rootY")); - final Root root4 = rootRepository.save(new Root().setStrValue("rootY")); + final Root root1 = rootRepository.save(new Root().setStrValue("rootx")); + final Root root2 = rootRepository.save(new Root().setStrValue("rootx")); + final Root root3 = rootRepository.save(new Root().setStrValue("rooty")); + final Root root4 = rootRepository.save(new Root().setStrValue("rooty")); final Root root5 = rootRepository.save(new Root()); // null - assertThat(filter("strValue==rootX")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("strValue==rootx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("strValue==nostr")).isEmpty(); - assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); - assertThat(filter("strValue!=rootX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + } + assertThat(filter("strValue!=rootx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); assertThat(filter("strValue!=nostr")).hasSize(5); - assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("strValuerootX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("strValue>=rootX")).hasSize(4); - assertThat(filter("strValue=in=rootX")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("strValue=in=(rootX, rootY)")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("strValue=in=(rootZ, rootT)")).isEmpty(); - assertThat(filter("strValue=out=rootX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - assertThat(filter("strValue=out=(rootX, rootY)")).hasSize(1).containsExactlyInAnyOrder(root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + } + assertThat(filter("strValuerootx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("strValue>=rootx")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + + assertThat(filter("strValue=in=rootx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("strValue=in=(rootx, rooty)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("strValue=in=(rootz, roott)")).isEmpty(); + assertThat(filter("strValue=out=rootx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("strValue=out=(rootx, rooty)")).hasSize(1).containsExactlyInAnyOrder(root5); // wildcard, like - assertThat(filter("strValue==root*")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("strValue==*tX")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("strValue==root*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("strValue==*tx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("strValue!=root*")).hasSize(1).containsExactlyInAnyOrder(root5); - assertThat(filter("strValue!=*tX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("strValue!=*tx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5); + + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // null checks + assertThat(filter("strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + } + + assertThat(filter("strValue==*tx and strValue==rooty")).isEmpty(); + assertThat(filter("strValue==*tx and strValue!=rootx")).isEmpty(); + assertThat(filter("strValue==*tx and strValue==rootx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("strValue==*tx or strValue==rooty")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("strValue==*tx or strValue!=rootx")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + assertThat(filter("strValue==*tx or strValue=is=null")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); + } } @Test void singularIntAttribute() { - final Root root = rootRepository.save(new Root().setIntValue(0)); + final Root root1 = rootRepository.save(new Root().setIntValue(0)); final Root root2 = rootRepository.save(new Root().setIntValue(0)); final Root root3 = rootRepository.save(new Root().setIntValue(1)); final Root root4 = rootRepository.save(new Root().setIntValue(1)); - assertThat(filter("intValue==0")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("intValue==2")).isEmpty(); assertThat(filter("intValue!=0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("intValue!=2")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("intValue<1")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); + assertThat(filter("intValue!=2")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("intValue<1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("intValue>0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("intValue>=0")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("intValue=in=0")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("intValue=in=(0, 1)")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); + assertThat(filter("intValue>=0")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("intValue=in=0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("intValue=in=(0, 1)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("intValue=in=(2, 3)")).isEmpty(); assertThat(filter("intValue=out=0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); assertThat(filter("intValue=out=(0, 1)")).isEmpty(); + + assertThat(filter("intValue==0 and intValue==1")).isEmpty(); + assertThat(filter("intValue==0 and intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("intValue==0 and intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("intValue==0 or intValue==1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("intValue==0 or intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); } @Test void singularEntityAttribute() { - final Sub sub = subRepository.save(new Sub().setStrValue("subX").setIntValue(0)); - final Sub sub2 = subRepository.save(new Sub().setStrValue("subY").setIntValue(1)); - final Root root = rootRepository.save(new Root().setSubEntity(sub)); - final Root root2 = rootRepository.save(new Root().setSubEntity(sub)); + final Sub sub1 = subRepository.save(new Sub().setStrValue("subx").setIntValue(0)); + final Sub sub2 = subRepository.save(new Sub().setStrValue("suby").setIntValue(1)); + final Root root1 = rootRepository.save(new Root().setSubEntity(sub1)); + final Root root2 = rootRepository.save(new Root().setSubEntity(sub1)); final Root root3 = rootRepository.save(new Root().setSubEntity(sub2)); final Root root4 = rootRepository.save(new Root().setSubEntity(sub2)); final Root root5 = rootRepository.save(new Root()); // no sub set // by sub entity string - assertThat(filter("subEntity.strValue==subX")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("subEntity.strValue==subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.strValue==nostr")).isEmpty(); // TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it is - assertThat(filter("subEntity.strValue!=subX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.strValue!=subx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); assertThat(filter("subEntity.strValue!=nostr")).hasSize(5); - assertThat(filter("subEntity.strValuesubX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("subEntity.strValue>=subX")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("subEntity.strValue=in=subX")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("subEntity.strValue=in=(subX, subY)")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); + assertThat(filter("subEntity.strValuesubx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subEntity.strValue>=subx")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("subEntity.strValue=in=subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.strValue=in=(subx, suby)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.strValue=in=(subZ, subT)")).isEmpty(); - assertThat(filter("subEntity.strValue=out=subX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - assertThat(filter("subEntity.strValue=out=(subX, subY)")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("subEntity.strValue=out=subx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.strValue=out=(subx, suby)")).hasSize(1).containsExactlyInAnyOrder(root5); // wildcard, like - assertThat(filter("subEntity.strValue==sub*")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); - assertThat(filter("subEntity.strValue==*bX")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("subEntity.strValue==sub*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("subEntity.strValue==*bx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.strValue!=sub*")).hasSize(1).containsExactlyInAnyOrder(root5); - assertThat(filter("subEntity.strValue!=*bX")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.strValue!=*bx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + assertThat(filter("subEntity.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5); + + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // null checks + assertThat(filter("subEntity.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5); + assertThat(filter("subEntity.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + } + + assertThat(filter("subEntity.strValue==*bx and subEntity.strValue==suby")).isEmpty(); + assertThat(filter("subEntity.strValue==*bx and subEntity.strValue!=subx")).isEmpty(); + assertThat(filter("subEntity.strValue==*bx and subEntity.strValue==subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.strValue==*bx or subEntity.strValue==suby")) + .hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null entity in not, and doesn't support =is= and =not= + assertThat(filter("subEntity.strValue==*bx or subEntity.strValue!=subx")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subEntity.strValue==*bx or subEntity.strValue=is=null")).hasSize(3) + .containsExactlyInAnyOrder(root1, root2, root5); + } // by sub entity int - assertThat(filter("subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root, root2); + assertThat(filter("subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); assertThat(filter("subEntity.intValue==2")).isEmpty(); - assertThat(filter("subEntity.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); - assertThat(filter("subEntity.intValue!=2")).hasSize(5); - assertThat(filter("subEntity.intValue<1")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("subEntity.intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null entity in not + assertThat(filter("subEntity.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + assertThat(filter("subEntity.intValue!=2")).hasSize(5); + } + assertThat(filter("subEntity.intValue<1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root3, root4); assertThat(filter("subEntity.intValue>=0")).hasSize(4); - assertThat(filter("subEntity.intValue=in=0")).hasSize(2).containsExactlyInAnyOrder(root, root2); - assertThat(filter("subEntity.intValue=in=(0, 1)")).hasSize(4).containsExactlyInAnyOrder(root, root2, root3, root4); + assertThat(filter("subEntity.intValue=in=0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.intValue=in=(0, 1)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); assertThat(filter("subEntity.intValue=in=(2, 3)")).isEmpty(); assertThat(filter("subEntity.intValue=out=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); assertThat(filter("subEntity.intValue=out=(0, 1)")).hasSize(1).containsExactlyInAnyOrder(root5); + + assertThat(filter("subEntity.intValue==0 and subEntity.intValue==1")).isEmpty(); + assertThat(filter("subEntity.intValue==0 and subEntity.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.intValue==0 and subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.intValue==0 or subEntity.intValue==1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null entity in not + assertThat(filter("subEntity.intValue==0 or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); + } + + assertThat(filter("subEntity.strValue==subx and subEntity.intValue==1")).isEmpty(); + assertThat(filter("subEntity.strValue==subx and subEntity.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.strValue==subx and subEntity.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2); + assertThat(filter("subEntity.strValue==subx or subEntity.intValue==1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null entity in not + assertThat(filter("subEntity.strValue==subx or subEntity.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); + } } @Test void pluralSubSetAttribute() { - final Sub sub = subRepository.save(new Sub().setStrValue("subX").setIntValue(0)); - final Sub sub2 = subRepository.save(new Sub().setStrValue("subY").setIntValue(1)); - final Sub sub3 = subRepository.save(new Sub().setStrValue("subY").setIntValue(0)); - final Root root = rootRepository.save(new Root().setSubSet(Set.of(sub))); + final Sub sub1 = subRepository.save(new Sub().setStrValue("subx").setIntValue(0)); + final Sub sub2 = subRepository.save(new Sub().setStrValue("suby").setIntValue(1)); + final Sub sub3 = subRepository.save(new Sub().setStrValue("suby").setIntValue(0)); + final Root root1 = rootRepository.save(new Root().setSubSet(Set.of(sub1))); final Root root2 = rootRepository.save(new Root().setSubSet(Set.of(sub2))); final Root root3 = rootRepository.save(new Root().setSubSet(Set.of(sub3))); - final Root root4 = rootRepository.save(new Root().setSubSet(Set.of(sub, sub2))); - final Root root5 = rootRepository.save(new Root().setSubSet(Set.of(sub, sub3))); + final Root root4 = rootRepository.save(new Root().setSubSet(Set.of(sub1, sub2))); + final Root root5 = rootRepository.save(new Root().setSubSet(Set.of(sub1, sub3))); final Root root6 = rootRepository.save(new Root()); // no sub set // by sub entity string - assertThat(filter("subSet.strValue==subX")).hasSize(3).containsExactlyInAnyOrder(root, root4, root5); + assertThat(filter("subSet.strValue==subx")).hasSize(3).containsExactlyInAnyOrder(root1, root4, root5); assertThat(filter("subSet.strValue==nostr")).isEmpty(); - assertThat(filter("subSet.strValue!=subX")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); + assertThat(filter("subSet.strValue!=subx")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); assertThat(filter("subSet.strValue!=nostr")).hasSize(6); - assertThat(filter("subSet.strValuesubX")).hasSize(4).containsExactlyInAnyOrder(root2, root3, root4, root5); - assertThat(filter("subSet.strValue>=subX")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subSet.strValue=in=subX")).hasSize(3).containsExactlyInAnyOrder(root, root4, root5); - assertThat(filter("subSet.strValue=in=(subX, subY)")) - .hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); + assertThat(filter("subSet.strValuesubx")).hasSize(4).containsExactlyInAnyOrder(root2, root3, root4, root5); + assertThat(filter("subSet.strValue>=subx")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subSet.strValue=in=subx")).hasSize(3).containsExactlyInAnyOrder(root1, root4, root5); + assertThat(filter("subSet.strValue=in=(subx, suby)")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subSet.strValue=in=(subZ, subT)")).isEmpty(); - assertThat(filter("subSet.strValue=out=subX")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); - assertThat(filter("subSet.strValue=out=(subX, subY)")).hasSize(1).containsExactlyInAnyOrder(root6); + assertThat(filter("subSet.strValue=out=subx")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); + assertThat(filter("subSet.strValue=out=(subx, suby)")).hasSize(1).containsExactlyInAnyOrder(root6); // wildcard, like - assertThat(filter("subSet.strValue==sub*")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subSet.strValue==*bX")).hasSize(3).containsExactlyInAnyOrder(root, root4, root5); + assertThat(filter("subSet.strValue==sub*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subSet.strValue==*bx")).hasSize(3).containsExactlyInAnyOrder(root1, root4, root5); assertThat(filter("subSet.strValue!=sub*")).hasSize(1).containsExactlyInAnyOrder(root6); - assertThat(filter("subSet.strValue!=*bX")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); + assertThat(filter("subSet.strValue!=*bx")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6); + assertThat(filter("subSet.strValue==*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subSet.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root6); + + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 treats it like same element shall match - which doesn't match to "has" semantic for sets + assertThat(filter("subSet.strValue==*bx and subSet.strValue==suby")).hasSize(2).containsExactlyInAnyOrder(root4, root5); + } + assertThat(filter("subSet.strValue==*bx and subSet.strValue!=subx")).isEmpty(); + assertThat(filter("subSet.strValue==*bx and subSet.strValue!=suby")).hasSize(1).containsExactlyInAnyOrder(root1); + assertThat(filter("subSet.strValue==*bx or subSet.strValue==suby")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null - has which shall be included as doesn't have + assertThat(filter("subSet.strValue==*bx or subSet.strValue!=subx")) + .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); + } // by sub entity int - assertThat(filter("subSet.intValue==0")).hasSize(4).containsExactlyInAnyOrder(root, root3, root4, root5); + assertThat(filter("subSet.intValue==0")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); assertThat(filter("subSet.intValue==2")).isEmpty(); - assertThat(filter("subSet.intValue!=0")).hasSize(2).containsExactlyInAnyOrder(root2, root6); - assertThat(filter("subSet.intValue!=2")).hasSize(6); - assertThat(filter("subSet.intValue<1")).hasSize(4).containsExactlyInAnyOrder(root, root3, root4, root5); - assertThat(filter("subSet.intValue<=1")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); + if (getRsqlToSpecBuilder() == G3) { + // the legacy builders has different (and wrong semantic) + // G1 - has element != x + // G2 - has element != x or has no elements + // accompanying G3 it is (as it should be) semantic of set != x - has no element with value x (including nhas no elements) + assertThat(filter("subSet.intValue!=0")).hasSize(2).containsExactlyInAnyOrder(root2, root6); + } + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null - has which shall be included as doesn't have + assertThat(filter("subSet.intValue!=2")).hasSize(6); + } + assertThat(filter("subSet.intValue<1")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); + assertThat(filter("subSet.intValue<=1")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subSet.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root2, root4); - assertThat(filter("subSet.intValue>=0")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subSet.intValue=in=0")).hasSize(4).containsExactlyInAnyOrder(root, root3, root4, root5); - assertThat(filter("subSet.intValue=in=(0, 1)")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); + assertThat(filter("subSet.intValue>=0")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subSet.intValue=in=0")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); + assertThat(filter("subSet.intValue=in=(0, 1)")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); assertThat(filter("subSet.intValue=in=(2, 3)")).isEmpty(); assertThat(filter("subSet.intValue=out=0")).hasSize(2).containsExactlyInAnyOrder(root2, root6); assertThat(filter("subSet.intValue=out=(0, 1)")).hasSize(1).containsExactlyInAnyOrder(root6); + + assertThat(filter("subSet.intValue==0 and subSet.intValue==0")).hasSize(4).containsExactlyInAnyOrder(root1, root3, root4, root5); + if (getRsqlToSpecBuilder() == G3) { + assertThat(filter("subSet.intValue==0 and subSet.intValue!=1")).hasSize(3).containsExactlyInAnyOrder(root1, root3, root5); + } + assertThat(filter("subSet.intValue==0 or subSet.intValue==1")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null - has which shall be included as doesn't have + assertThat(filter("subSet.intValue==0 or subSet.intValue!=1")) + .hasSize(5).containsExactlyInAnyOrder(root1, root3, root4, root5, root6); + } + + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 treats it like same element shall match - which doesn't match to "has" semantic for sets + assertThat(filter("subSet.intValue==0 and subSet.strValue==suby")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5); + } + assertThat(filter("subSet.intValue==0 and subSet.strValue!=subx")).hasSize(1).containsExactlyInAnyOrder(root3); + assertThat(filter("subSet.intValue==0 and subSet.strValue!=suby")).hasSize(1).containsExactlyInAnyOrder(root1); + assertThat(filter("subSet.intValue==0 or subSet.strValue==suby")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // G1 doesn't get null - has which shall be included as doesn't have + assertThat(filter("subSet.intValue==0 or subSet.strValue!=subx")) + .hasSize(6).containsExactlyInAnyOrder(root1, root2, root3, root4, root5, root6); + } } @Test void pluralSubMapAttribute() { - final Root root = rootRepository.save(new Root().setSubMap(Map.of("x", "rootX", "y", "rootY"))); - final Root root2 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootX", "y", "rootX"))); - final Root root3 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootY", "y", "rootY"))); - final Root root4 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootY", "y", "rootX"))); - final Root root5 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootX"))); + final Root root1 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootx", "y", "rooty"))); + final Root root2 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootx", "y", "rootx"))); + final Root root3 = rootRepository.save(new Root().setSubMap(Map.of("x", "rooty", "y", "rooty"))); + final Root root4 = rootRepository.save(new Root().setSubMap(Map.of("x", "rooty", "y", "rootx"))); + final Root root5 = rootRepository.save(new Root().setSubMap(Map.of("x", "rootx"))); final Root root6 = rootRepository.save(new Root()); // no sub map - assertThat(filter("subMap.x==rootX")).hasSize(3).containsExactlyInAnyOrder(root, root2, root5); + assertThat(filter("subMap.x==rootx")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); assertThat(filter("subMap.x==nostr")).isEmpty(); // TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not - assertThat(filter("subMap.x!=rootX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("subMap.x!=nostr")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subMap.xrootX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("subMap.x>=rootX")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subMap.x=in=rootX")).hasSize(3).containsExactlyInAnyOrder(root, root2, root5); - assertThat(filter("subMap.x=in=(rootX, rootY)")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subMap.x=in=(rootZ, rootT)")).isEmpty(); - assertThat(filter("subMap.x=out=rootX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); - assertThat(filter("subMap.x=out=(rootX, rootY)")).isEmpty(); + assertThat(filter("subMap.x!=rootx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subMap.x!=nostr")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.xrootx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subMap.x>=rootx")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.x=in=rootx")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); + assertThat(filter("subMap.x=in=(rootx, rooty)")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.x=in=(rootz, roott)")).isEmpty(); + assertThat(filter("subMap.x=out=rootx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subMap.x=out=(rootx, rooty)")).isEmpty(); // wildcard, like - assertThat(filter("subMap.x==root*")).hasSize(5).containsExactlyInAnyOrder(root, root2, root3, root4, root5); - assertThat(filter("subMap.x==*tX")).hasSize(3).containsExactlyInAnyOrder(root, root2, root5); + assertThat(filter("subMap.x==root*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.x==*tx")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5); assertThat(filter("subMap.x!=root*")).isEmpty(); - assertThat(filter("subMap.x!=*tX")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subMap.x!=*tx")).hasSize(2).containsExactlyInAnyOrder(root3, root4); + assertThat(filter("subMap.x==*")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.x!=*")).isEmpty(); + + if (getRsqlToSpecBuilder() != LEGACY_G1) { + // null checks + assertThat(filter("subMap.x=is=null")).hasSize(1).containsExactlyInAnyOrder(root6); + assertThat(filter("subMap.x=not=null")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + } + + assertThat(filter("subMap.x==*tx and subMap.y==rooty")).hasSize(1).containsExactlyInAnyOrder(root1); + assertThat(filter("subMap.x==*tx and subMap.y!=rootx")).hasSize(1).containsExactlyInAnyOrder(root1); + assertThat(filter("subMap.x==*tx or subMap.x==rooty")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); + assertThat(filter("subMap.x==*tx or subMap.x!=rootx")) + .hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5); } private List filter(final String rsql) { // reference / auto filter (using elements and reflection) final ReferenceMatcher matcher = ReferenceMatcher.ofRsql(rsql); final List refResult = StreamSupport.stream(rootRepository.findAll().spliterator(), false).filter(matcher::match).toList(); - final List result = rootRepository.findAll(builder.specification(RsqlParser.parse(rsql))); + final List result = rootRepository.findAll(getSpecification(rsql)); // auto check with reference result try { assertThat(result).containsExactlyInAnyOrder(refResult.toArray(Root[]::new)); @@ -246,6 +382,14 @@ class SpecificationBuilderTest { return result; } + protected Specification getSpecification(final String rsql) { + return builder.specification(RsqlParser.parse(rsql)); + } + + private static RsqlConfigHolder.RsqlToSpecBuilder getRsqlToSpecBuilder() { + return RsqlConfigHolder.getInstance().getRsqlToSpecBuilder(); + } + @SpringBootConfiguration static class Config {} } \ No newline at end of file