* Rsql G3
* Move legacy and G2 visors in rsqllegacy and deprecate
* Refactor RSQLUtility

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-06-02 10:08:13 +03:00
committed by GitHub
parent a8a67910ac
commit c3aa2b7ae7
18 changed files with 1330 additions and 235 deletions

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.management;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.fail;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
import java.net.URI;
import java.util.ArrayList;
@@ -1030,7 +1031,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test that RSQL filter finds targets with tag and metadata.")
void findTargetsByRsqlWithTypeAndMetadata() {
if (RsqlConfigHolder.getInstance().isLegacyRsqlVisitor()) {
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G1) {
// legacy visitor fail with that
return;
}

View File

@@ -0,0 +1,38 @@
/**
* 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;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class NodeTest {
@Test
void testSimplify() {
final Node aEqB = new Node.Comparison("a", Node.Comparison.Operator.EQ, "b");
final Node aEqC = new Node.Comparison("a", Node.Comparison.Operator.EQ, "c");
final Node aEqD = new Node.Comparison("a", Node.Comparison.Operator.EQ, "d");
final Node aEqE = new Node.Comparison("a", Node.Comparison.Operator.EQ, "e");
assertThat(aEqB.and(aEqC).and(aEqD).and(aEqE).getChildren())
.hasSize(4)
.containsExactly(aEqB, aEqC, aEqD, aEqE);
assertThat(aEqB.and(aEqC).and(aEqD.and(aEqE)).getChildren())
.hasSize(4)
.containsExactly(aEqB, aEqC, aEqD, aEqE);
assertThat(aEqB.or(aEqC).or(aEqD).or(aEqE).getChildren())
.hasSize(4)
.containsExactly(aEqB, aEqC, aEqD, aEqE);
assertThat(aEqB.or(aEqC).or(aEqD.or(aEqE)).getChildren())
.hasSize(4)
.containsExactly(aEqB, aEqC, aEqD, aEqE);
}
}

View File

@@ -11,6 +11,8 @@ package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
import java.util.Arrays;
import java.util.Map;
@@ -29,6 +31,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
import org.junit.jupiter.api.BeforeEach;
@@ -176,9 +179,11 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null==null", 1); // "null" check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a==null", 0); // "null" check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=is=value.dot", 1);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=is=null", 5); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=is=null", 1 + 5); // null check
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() != LEGACY_G1) {
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=is=value.dot", 1);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=is=null", 5); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=is=null", 1 + 5); // null check
}
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=eq=value.dot", 1);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=eq=null", 5); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=eq=null", 1 + 5); // null check
@@ -188,14 +193,24 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null!=null2", 1); // value check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a!=null", 0); // "null" check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=not=value.dot", 5);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null", 1); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null2", 1 + 5); // value check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=not=null", 0); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.dot=ne=value.dot", 5);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null", 1); // null check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null2", 1 + 5); // value check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=ne=null", 0); // null check
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() != LEGACY_G1) {
assertRSQLQuery(
TargetFields.ATTRIBUTE.name() + ".test.dot=not=value.dot",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 5 : 0);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=not=null", 1); // null check
assertRSQLQuery(
TargetFields.ATTRIBUTE.name() + ".test.null=not=null2",
1 + (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 5 : 0)); // value check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=not=null", 0); // null check
assertRSQLQuery(
TargetFields.ATTRIBUTE.name() + ".test.dot=ne=value.dot",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 5 : 0);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.null=ne=null", 1); // null check
assertRSQLQuery(
TargetFields.ATTRIBUTE.name() + ".test.null=ne=null2",
1 + (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 5 : 0)); // value check
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".test.n/a=ne=null", 0); // null check
}
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision==1.1", 1);
assertRSQLQuery(TargetFields.ATTRIBUTE.name() + ".revision!=1.1", 1);
@@ -245,6 +260,8 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.TAG.name() + "!=notexist", 5);
assertRSQLQuery(TargetFields.TAG.name() + "==''", 1);
assertRSQLQuery(TargetFields.TAG.name() + "!=''", 4);
assertRSQLQuery(TargetFields.TAG.name() + "!=*", 1); // has no tags
assertRSQLQuery(TargetFields.TAG.name() + "==*", 4); // has tags
assertRSQLQuery(TargetFields.TAG.name() + "=in=(Tag1,notexist)", 2);
assertRSQLQuery(TargetFields.TAG.name() + "=in=(null)", 0);
assertRSQLQuery(TargetFields.TAG.name() + "=out=(Tag1,notexist)", 3);
@@ -278,11 +295,13 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey==noExist*", 0);
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist==metaValue", 0);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=metaValue", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=null", 4); // null check (1 of the initial five has)
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=*v*", 2);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=noExist*", 0);
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=is=null", 1 + 5); // null check
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() != LEGACY_G1) {
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=metaValue", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=null", 4); // null check (1 of the initial five has)
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=*v*", 2);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=is=noExist*", 0);
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=is=null", 1 + 5); // null check
}
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=metaValue", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=null", 4); // null check (1 of the initial five has)
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=eq=*v*", 2);
@@ -293,15 +312,29 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey!=null", 2);
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist!=metaValue", 0);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey!=notExist", 2);
//
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=metaValue", 1 + 4);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=null", 2); // null check (2 of the initial five)
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=not=metaValue", 1 + 5);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=notExist", 1 + 5);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=metaValue", 1 + 4);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=null", 2); // null check (2 of the initial five)
assertRSQLQuery(TargetFields.METADATA.name() + ".notExist=ne=metaValue", 1 + 5);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=notExist", 1 + 5);
if (RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() != LEGACY_G1) {
assertRSQLQuery(
TargetFields.METADATA.name() + ".metaKey=not=metaValue",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 1 + 4 : 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=not=null", 2); // null check (2 of the initial five)
assertRSQLQuery(
TargetFields.METADATA.name() + ".notExist=not=metaValue",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 1 + 5 : 0);
assertRSQLQuery(
TargetFields.METADATA.name() + ".metaKey=not=notExist",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 2 + 4 : 2);
assertRSQLQuery(
TargetFields.METADATA.name() + ".metaKey=ne=metaValue",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 1 + 4 : 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=ne=null", 2); // null check (2 of the initial five)
assertRSQLQuery(
TargetFields.METADATA.name() + ".notExist=ne=metaValue",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 1 + 5 : 0);
assertRSQLQuery(
TargetFields.METADATA.name() + ".metaKey=ne=notExist",
RsqlConfigHolder.getInstance().getRsqlToSpecBuilder() == LEGACY_G2 ? 2 + 4 : 2);
}
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=in=(metaValue,notexist)", 1);
assertRSQLQuery(TargetFields.METADATA.name() + ".metaKey=out=(metaValue,notexist)", 1);

View File

@@ -9,25 +9,16 @@
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import java.util.List;
import jakarta.persistence.EntityManager;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import cz.jirutka.rsql.parser.RSQLParser;
import cz.jirutka.rsql.parser.ast.Node;
import cz.jirutka.rsql.parser.ast.RSQLOperators;
import cz.jirutka.rsql.parser.ast.RSQLVisitor;
import org.eclipse.hawkbit.repository.RsqlQueryField;
import org.eclipse.hawkbit.repository.jpa.Utils;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.CollectionUtils;
public class RSQLToSQL {
@@ -39,50 +30,27 @@ public class RSQLToSQL {
}
public <T, A extends Enum<A> & RsqlQueryField> String toSQL(
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor);
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final RsqlToSpecBuilder rsqlToSpecBuilder) {
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, rsqlToSpecBuilder);
final TypedQuery<?> typedQuery = entityManager.createQuery(query);
return Utils.toSql(typedQuery);
}
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final RsqlToSpecBuilder rsqlToSpecBuilder) {
final CriteriaQuery<T> query = entityManager.getCriteriaBuilder().createQuery(domainClass);
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
return query.where(
RsqlConfigHolder.getInstance().isLegacyRsqlVisitor() == legacyRsqlVisitor ?
// use directly
RSQLUtility.<A, T> buildRsqlSpecification(rsql, fieldsClass, null, DATABASE)
.toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb) :
toPredicate(rsql, fieldsClass, null,
query.from(domainClass), cb.createQuery(domainClass), cb, legacyRsqlVisitor)
);
}
private <T, A extends Enum<A> & RsqlQueryField> Predicate toPredicate(
final String rsql,
final Class<A> fieldsClass, final VirtualPropertyReplacer virtualPropertyReplacer,
final Root<T> root, final CriteriaQuery<?> query, final CriteriaBuilder cb,
final boolean legacyRsqlVisitor) {
final Node rootNode = new RSQLParser(RSQLOperators.defaultOperators()).parse(rsql);
query.distinct(true);
final RSQLVisitor<List<Predicate>, String> jpqQueryRSQLVisitor =
legacyRsqlVisitor
? new JpaQueryRsqlVisitor<>(
root, cb, fieldsClass,
virtualPropertyReplacer, DATABASE, query,
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase())
: new JpaQueryRsqlVisitorG2<>(
fieldsClass, root, query, cb,
DATABASE, virtualPropertyReplacer,
!RsqlConfigHolder.getInstance().isCaseInsensitiveDB() && RsqlConfigHolder.getInstance().isIgnoreCase());
final List<Predicate> accept = rootNode.accept(jpqQueryRSQLVisitor);
if (CollectionUtils.isEmpty(accept)) {
return cb.conjunction();
} else {
return cb.and(accept.toArray(new Predicate[0]));
final RsqlToSpecBuilder defaultRsqlToSpecBuilder = RsqlConfigHolder.getInstance().getRsqlToSpecBuilder();
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(rsqlToSpecBuilder);
}
try {
return query.where(RSQLUtility.<A, T> buildRsqlSpecification(rsql, fieldsClass, null, DATABASE)
.toPredicate(query.from(domainClass), cb.createQuery(domainClass), cb));
} finally {
if (defaultRsqlToSpecBuilder != rsqlToSpecBuilder) {
RsqlConfigHolder.getInstance().setRsqlToSpecBuilder(defaultRsqlToSpecBuilder);
}
}
}
}

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.rsql;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.G3;
import static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
import jakarta.persistence.EntityManager;
@@ -42,6 +45,11 @@ class RSQLToSQLTest {
private static final boolean FULL = Boolean.getBoolean("full");
private RSQLToSQL rsqlToSQL;
@Test
void p() {
print(JpaTarget.class, TargetFields.class, TargetFields.TAG.name() + "==''");
print(JpaTarget.class, TargetFields.class, TargetFields.TAG.name() + "!=''");
}
@Test
void print() {
print(JpaTarget.class, TargetFields.class, "tag==tag1 and tag==tag2");
@@ -105,18 +113,28 @@ class RSQLToSQLTest {
private <T, A extends Enum<A> & RsqlQueryField> void print(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql) {
System.out.println(rsql);
System.out.println("\tlegacy:\n" +
"\t\t" + useStar(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, true)));
System.out.println("\tG2:\n" +
"\t\t" + useStar(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, false)));
final String legacy = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G1);
final String g2 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G2);
final String g3 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, G3);
System.out.println("\tlegacy:\n\t\t" + useStar(legacy));
System.out.println("\tG2:\n\t\t" + useStar(g2));
System.out.println("\tG3:\n\t\t" + useStar(g3));
if (!g2.equals(g3)) {
System.out.println("\tG2 != G3 for rsql: " + rsql);
}
}
private <T, A extends Enum<A> & RsqlQueryField> void printFrom(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql) {
System.out.println(rsql);
System.out.println("\tlegacy:\n" +
"\t\t" + from(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, true)));
System.out.println("\tG2:\n" +
"\t\t" + from(rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, false)));
final String legacy = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G1);
final String g2 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, LEGACY_G2);
final String g3 = rsqlToSQL.toSQL(domainClass, fieldsClass, rsql, G3);
System.out.println("\tlegacy:\n\t\t" + from(legacy));
System.out.println("\tG2:\n\t\t" + from(g2));
System.out.println("\tG2:\n\t\t" + from(g3));
if (!g2.equals(g3)) {
System.out.println("\tG2 != G3 for rsql: " + rsql);
}
}
private static String useStar(final String sql) {

View File

@@ -55,6 +55,7 @@ import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -70,6 +71,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@Feature("Component Tests - Repository")
@Story("RSQL search utility")
@Disabled
// TODO: fully document tests -> @Description for long text and reasonable
// method name as short text
class RSQLUtilityTest {

View File

@@ -0,0 +1,95 @@
/**
* 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;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser.parse;
import org.eclipse.hawkbit.repository.TargetFields;
import org.junit.jupiter.api.Test;
class RsqlParserTest {
@Test
void testSimpleComparisonNode() {
assertThat(parse("a == 6")).hasToString("a == \"6\"");
assertThat(parse("a != 6")).hasToString("a != \"6\"");
assertThat(parse("a > 6")).hasToString("a > \"6\"");
assertThat(parse("a >= 6")).hasToString("a >= \"6\"");
assertThat(parse("a < 6")).hasToString("a < \"6\"");
assertThat(parse("a <= 6")).hasToString("a <= \"6\"");
assertThat(parse("a =in= 6")).hasToString("a in (\"6\")");
assertThat(parse("a =in= (6)")).hasToString("a in (\"6\")");
assertThat(parse("a =in= (6, 7)")).hasToString("a in (\"6\", \"7\")");
assertThat(parse("a =out= 6")).hasToString("a not in (\"6\")");
assertThat(parse("a =out= (6)")).hasToString("a not in (\"6\")");
assertThat(parse("a =out= (6, 7)")).hasToString("a not in (\"6\", \"7\")");
}
@Test
void testNullComparison() {
assertThat(parse("a == null")).hasToString("a == \"null\"");
assertThat(parse("a =is= 6")).hasToString("a == \"6\"");
assertThat(parse("a =is= null")).hasToString("a == null");
assertThat(parse("a != null")).hasToString("a != \"null\"");
assertThat(parse("a =not= 6")).hasToString("a != \"6\"");
assertThat(parse("a =not= null")).hasToString("a != null");
}
@Test
void testLikeComparison() {
assertThat(parse("a == test*")).hasToString("a like \"test*\"");
assertThat(parse("a == test\\*")).hasToString("a == \"test\\*\"");
assertThat(parse("a != test*")).hasToString("a not like \"test*\"");
assertThat(parse("a != test\\*")).hasToString("a != \"test\\*\"");
}
@Test
void testOrLogical() {
assertThat(parse("a == 6 or a == 7")).hasToString("a == \"6\" || a == \"7\"");
assertThat(parse("a == 6 or a == 7 or a == '8'")).hasToString("a == \"6\" || a == \"7\" || a == \"8\"");
assertThat(parse("(a == 6 or a == 7) or a == '8'")).hasToString("a == \"6\" || a == \"7\" || a == \"8\"");
assertThat(parse("a == 6 or (a == 7 or a == '8')")).hasToString("a == \"6\" || a == \"7\" || a == \"8\"");
assertThat(parse("a == 6 or a == 7 or (a == '8')")).hasToString("a == \"6\" || a == \"7\" || a == \"8\"");
}
@Test
void testAndLogical() {
assertThat(parse("a == 6 and a == 7")).hasToString("a == \"6\" && a == \"7\"");
assertThat(parse("a == 6 and a == 7 and a == '8'")).hasToString("a == \"6\" && a == \"7\" && a == \"8\"");
assertThat(parse("(a == 6 and a == 7) and a == '8'")).hasToString("a == \"6\" && a == \"7\" && a == \"8\"");
assertThat(parse("a == 6 and (a == 7 and a == '8')")).hasToString("a == \"6\" && a == \"7\" && a == \"8\"");
assertThat(parse("a == 6 and a == 7 and (a == '8')")).hasToString("a == \"6\" && a == \"7\" && a == \"8\"");
}
@Test
void testComplexLogical() {
assertThat(parse("a == 6 and a == 7 or a == '8'")).hasToString("a == \"6\" && a == \"7\" || a == \"8\"");
assertThat(parse("(a == 6 or a == 7) and a == '8'")).hasToString("(a == \"6\" || a == \"7\") && a == \"8\"");
assertThat(parse("a == 6 or (a == 7 and a == '8')")).hasToString("a == \"6\" || a == \"7\" && a == \"8\"");
assertThat(parse("a == 6 and (a == 7 or a == '8')")).hasToString("a == \"6\" && (a == \"7\" || a == \"8\")");
}
@Test
void testQueryFieldsComparisonNode() {
// simple attribute
assertThat(parse("controllerid == 6", TargetFields.class)).hasToString("controllerId == \"6\"");
// reference attribute
assertThat(parse("assignedds.name == 6", TargetFields.class)).hasToString("assignedDistributionSet.name == \"6\"");
// reference attribute with default sub-attribute
assertThat(parse("tag == 6", TargetFields.class)).hasToString("tags.name == \"6\"");
assertThat(parse("tag.name == 6", TargetFields.class)).hasToString("tags.name == \"6\"");
// map attribute
assertThat(parse("metadata.x == 6", TargetFields.class)).hasToString("metadata.x == \"6\"");
assertThat(parse("metadata.x.y.z == 6", TargetFields.class)).hasToString("metadata.x.y.z == \"6\""); // with dots
}
}