Return back legacy RSQL visitor until G2 maturity (#1825)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-08-27 13:16:28 +03:00
committed by GitHub
parent d9d4469a95
commit de12d9b3de
4 changed files with 165 additions and 1 deletions

View File

@@ -42,8 +42,11 @@ public final class RsqlConfigHolder {
@Autowired
private RsqlVisitorFactory rsqlVisitorFactory;
/**
* @deprecated in favour of G2 RSQL visitor.
*/
@Deprecated
@Value("${hawkbit.rsql.legacyRsqlVisitor:false}")
@Value("${hawkbit.rsql.legacyRsqlVisitor:true}")
private boolean legacyRsqlVisitor;
/**

View File

@@ -72,6 +72,7 @@ import org.eclipse.hawkbit.repository.model.TargetMetadata;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
@@ -1291,6 +1292,10 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
@Test
@Description("Test that RSQL filter finds targets with tag and metadata.")
void findTargetsByRsqlWithTypeAndMetadata() {
if (RsqlConfigHolder.getInstance().isLegacyRsqlVisitor()) {
// legacy visitor fail with that
return;
}
final String controllerId1 = "target1";
final String controllerId2 = "target2";
createTargetWithMetadata(controllerId1, 2);

View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2024 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 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 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 org.eclipse.hawkbit.repository.FieldNameProvider;
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.jpa.JpaQuery;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.util.CollectionUtils;
import java.util.List;
public class RSQLToSQL {
private static final Database DATABASE = Database.H2;
private final EntityManager entityManager;
public RSQLToSQL(final EntityManager entityManager) {
this.entityManager = entityManager;
}
public <T, A extends Enum<A> & FieldNameProvider> String toSQL(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
return createDbQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor).getSQLString();
}
public <T, A extends Enum<A> & FieldNameProvider> DatabaseQuery createDbQuery(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
final CriteriaQuery<T> query = createQuery(domainClass, fieldsClass, rsql, legacyRsqlVisitor);
final TypedQuery<?> typedQuery = entityManager.createQuery(query);
// executes the query - otherwise the SQL string is not generated
typedQuery.setParameter(PersistenceUnitProperties.MULTITENANT_PROPERTY_DEFAULT, "DEFAULT");
typedQuery.getResultList();
return typedQuery.unwrap(JpaQuery.class).getDatabaseQuery();
}
private <T, A extends Enum<A> & FieldNameProvider> CriteriaQuery<T> createQuery(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql, final boolean legacyRsqlVisitor) {
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> & FieldNameProvider> 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]));
}
}
}

View File

@@ -0,0 +1,61 @@
/**
* Copyright (c) 2024 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 jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
@ActiveProfiles("test")
@SpringBootTest(webEnvironment=NONE, properties = {
"spring.main.allow-bean-definition-overriding=true",
"spring.main.banner-mode=off",
"logging.level.root=ERROR" })
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class })
@Import(TestChannelBinderConfiguration.class)
@Disabled("For manual run only, while playing around with RSQL to SQL")
public class RSQLToSQLTest {
private RSQLToSQL rsqlToSQL;
@PersistenceContext
private void setEntityManager(final EntityManager entityManager) {
rsqlToSQL = new RSQLToSQL(entityManager);
}
@Test
public void print() {
String rsql = "tag==tag1 or tag==tag2 or tag==tag3";
System.out.println(rsql + "\n" +
"\tlegacy:\n" +
"\t\t" + rsqlToSQL.toSQL(JpaTarget.class, TargetFields.class, rsql, true) + "\n" +
"\tG2:\n" +
"\t\t" + rsqlToSQL.toSQL(JpaTarget.class, TargetFields.class, rsql, false));
rsql = "targettype.key==type1 and metadata.key1==target1-value1";
System.out.println(rsql + "\n" +
"\tlegacy:\n" +
"\t\t" + rsqlToSQL.toSQL(JpaTarget.class, TargetFields.class, rsql, true) + "\n" +
"\tG2:\n" +
"\t\t" + rsqlToSQL.toSQL(JpaTarget.class, TargetFields.class, rsql, false));
}
}