Move Query Language (RSQL) in separate package and add Entity Matcher (#2531)
* Move Query Language (RSQL) in separate package - hawkbit-repository-ql * Add Entity Matcher which match an entity object agains filter * Spec to string utils now in runtime (as a library) - could be used in tests or to dump something in runtimes * Move eclipselink/hibernate profiles in new QL module, this way provided / set to hawkbit-repository-jpa * Remove unused javax.el imports
This commit is contained in:
@@ -12,7 +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 static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
@@ -69,7 +69,7 @@ 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.model.TargetUpdateStatus;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||
import org.eclipse.hawkbit.repository.jpa.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;
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.persistence.Query;
|
||||
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.metamodel.mapping.MappingModelExpressible;
|
||||
import org.hibernate.query.spi.QueryEngine;
|
||||
import org.hibernate.query.spi.QueryParameterImplementor;
|
||||
import org.hibernate.query.sqm.internal.QuerySqmImpl;
|
||||
import org.hibernate.query.sqm.internal.SqmUtil;
|
||||
import org.hibernate.query.sqm.spi.SqmParameterMappingModelResolutionAccess;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslation;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslator;
|
||||
import org.hibernate.query.sqm.sql.SqmTranslatorFactory;
|
||||
import org.hibernate.query.sqm.tree.SqmDmlStatement;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmParameter;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
|
||||
import org.hibernate.sql.ast.tree.MutationStatement;
|
||||
import org.hibernate.sql.ast.tree.Statement;
|
||||
import org.hibernate.sql.ast.tree.select.SelectStatement;
|
||||
import org.hibernate.sql.exec.spi.JdbcParameterBindings;
|
||||
import org.hibernate.sql.exec.spi.JdbcParametersList;
|
||||
|
||||
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||
@Slf4j
|
||||
public class HibernateUtils {
|
||||
|
||||
private static final Method getSqmTranslatorFactory;
|
||||
|
||||
static {
|
||||
Method method = null;
|
||||
try {
|
||||
method = QueryEngine.class.getMethod("getSqmTranslatorFactory");
|
||||
} catch (final NoSuchMethodException e) {
|
||||
log.warn("Can't resolve getSqmTranslatorFactory method (Utils.toString won't work)", e);
|
||||
}
|
||||
getSqmTranslatorFactory = method;
|
||||
}
|
||||
|
||||
public static String toSql(final Query query) {
|
||||
if (getSqmTranslatorFactory == null) {
|
||||
throw new UnsupportedOperationException("SqmTranslatorFactory resolver is not available");
|
||||
}
|
||||
|
||||
final QuerySqmImpl<?> hqlQuery = query.unwrap(QuerySqmImpl.class);
|
||||
final SessionFactoryImplementor factory = hqlQuery.getSessionFactory();
|
||||
final SharedSessionContractImplementor session = hqlQuery.getSession();
|
||||
final SessionFactoryImplementor sessionFactory = session.getFactory();
|
||||
|
||||
final SqmTranslatorFactory sqmTranslatorFactory;
|
||||
try {
|
||||
sqmTranslatorFactory = (SqmTranslatorFactory) getSqmTranslatorFactory.invoke(factory.getQueryEngine());
|
||||
} catch (final IllegalAccessException | InvocationTargetException e) {
|
||||
throw new UnsupportedOperationException("Can't create SqmTranslatorFactory", e);
|
||||
}
|
||||
|
||||
final SqmTranslator<? extends Statement> sqmSelectTranslator =
|
||||
hqlQuery.getSqmStatement() instanceof SqmSelectStatement<?> selectStatement
|
||||
? sqmTranslatorFactory.createSelectTranslator(selectStatement,
|
||||
hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(),
|
||||
hqlQuery.getLoadQueryInfluencers(), sessionFactory, false)
|
||||
: sqmTranslatorFactory.createMutationTranslator((SqmDmlStatement<?>) hqlQuery.getSqmStatement(),
|
||||
hqlQuery.getQueryOptions(), hqlQuery.getDomainParameterXref(), hqlQuery.getQueryParameterBindings(),
|
||||
hqlQuery.getLoadQueryInfluencers(), sessionFactory);
|
||||
|
||||
final SqmTranslation<? extends Statement> sqmTranslation = sqmSelectTranslator.translate();
|
||||
final SqlAstTranslatorFactory sqlAstTranslatorFactory = factory.getJdbcServices().getJdbcEnvironment().getSqlAstTranslatorFactory();
|
||||
final Map<QueryParameterImplementor<?>, Map<SqmParameter<?>, List<JdbcParametersList>>> jdbcParamsXref = SqmUtil.generateJdbcParamsXref(
|
||||
hqlQuery.getDomainParameterXref(), sqmTranslation::getJdbcParamsBySqmParam);
|
||||
|
||||
final JdbcParameterBindings jdbcParameterBindings = SqmUtil.createJdbcParameterBindings(hqlQuery.getQueryParameterBindings(),
|
||||
hqlQuery.getDomainParameterXref(), jdbcParamsXref, factory.getRuntimeMetamodels().getMappingMetamodel(),
|
||||
sqmSelectTranslator.getFromClauseAccess()::findTableGroup, new SqmParameterMappingModelResolutionAccess() {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> MappingModelExpressible<T> getResolvedMappingModelType(final SqmParameter<T> parameter) {
|
||||
return (MappingModelExpressible<T>) sqmTranslation.getSqmParameterMappingModelTypeResolutions().get(parameter);
|
||||
}
|
||||
}, hqlQuery.getSession());
|
||||
return (sqmTranslation.getSqlAst() instanceof SelectStatement selectStatement
|
||||
? sqlAstTranslatorFactory.buildSelectTranslator(factory, selectStatement)
|
||||
.translate(jdbcParameterBindings, hqlQuery.getQueryOptions())
|
||||
: sqlAstTranslatorFactory.buildMutationTranslator(factory, (MutationStatement) sqmTranslation.getSqlAst())
|
||||
.translate(jdbcParameterBindings, hqlQuery.getQueryOptions()))
|
||||
.getSqlString();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.rsql;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.eclipse.hawkbit.repository.jpa.ql.Node;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class NodeTest {
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* 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 java.lang.reflect.InvocationTargetException;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.Query;
|
||||
import jakarta.persistence.TypedQuery;
|
||||
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||
import jakarta.persistence.criteria.CriteriaQuery;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder;
|
||||
import org.springframework.orm.jpa.vendor.Database;
|
||||
|
||||
public class RSQLToSQL {
|
||||
|
||||
private static final Database DATABASE = Database.H2;
|
||||
private final EntityManager entityManager;
|
||||
private final boolean isEclipselink;
|
||||
|
||||
public RSQLToSQL(final EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
isEclipselink = entityManager.getProperties().keySet().stream().anyMatch(key -> key.startsWith("eclipselink."));
|
||||
}
|
||||
|
||||
public <T, A extends Enum<A> & RsqlQueryField> String toSQL(
|
||||
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);
|
||||
if (isEclipselink) {
|
||||
try {
|
||||
return (String)Class.forName("org.eclipse.hawkbit.repository.jpa.EclipselinkUtils")
|
||||
.getMethod("toSql", Query.class)
|
||||
.invoke(null, typedQuery);
|
||||
} catch (final IllegalAccessException | NoSuchMethodException | ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
} catch (final InvocationTargetException e) {
|
||||
if (e.getCause() instanceof RuntimeException re) {
|
||||
throw re;
|
||||
} else {
|
||||
throw new IllegalStateException(e.getCause());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return HibernateUtils.toSql(typedQuery);
|
||||
}
|
||||
}
|
||||
|
||||
private <T, A extends Enum<A> & RsqlQueryField> CriteriaQuery<T> createQuery(
|
||||
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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter actions
|
||||
*/
|
||||
class RSQLActionFieldsTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlActionFieldsTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private JpaTarget target;
|
||||
private JpaAction action;
|
||||
@@ -26,7 +26,7 @@ import org.springframework.data.domain.PageRequest;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter rollout group
|
||||
*/
|
||||
class RSQLRolloutFieldTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlRolloutFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private Rollout rollout;
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter rollout group
|
||||
*/
|
||||
class RSQLRolloutGroupFieldTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlRolloutGroupFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private Long rolloutGroupId;
|
||||
private Rollout rollout;
|
||||
@@ -27,7 +27,7 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter software module
|
||||
*/
|
||||
class RSQLSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlSoftwareModuleFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private SoftwareModule ah;
|
||||
|
||||
@@ -24,7 +24,7 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter software module test type
|
||||
*/
|
||||
class RSQLSoftwareModuleTypeFieldsTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlSoftwareModuleTypeFieldsTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
/**
|
||||
* Test filter software module test type by id
|
||||
@@ -25,7 +25,7 @@ import org.springframework.data.domain.PageRequest;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter target and distribution set tags
|
||||
*/
|
||||
class RSQLTagFieldsTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlTagFieldsTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
@BeforeEach
|
||||
void seuptBeforeTest() {
|
||||
@@ -11,8 +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 static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
@@ -28,7 +28,6 @@ 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;
|
||||
@@ -41,7 +40,7 @@ import org.springframework.data.domain.Slice;
|
||||
* Story: RSQL filter target
|
||||
*/
|
||||
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
||||
class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlTargetFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private static final String OR = ",";
|
||||
private static final String AND = ";";
|
||||
@@ -387,26 +386,26 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
||||
*/
|
||||
@Test
|
||||
void rsqlValidTargetFields() {
|
||||
RSQLUtility.validateRsqlFor(
|
||||
RsqlUtility.validateRsqlFor(
|
||||
"ID == '0123' and NAME == abcd and DESCRIPTION == absd and CREATEDAT =lt= 0123 and LASTMODIFIEDAT =gt= 0123" +
|
||||
" and CONTROLLERID == 0123 and UPDATESTATUS == PENDING and IPADDRESS == 0123 and LASTCONTROLLERREQUESTAT == 0123" +
|
||||
" and tag == beta",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager);
|
||||
RSQLUtility.validateRsqlFor(
|
||||
RsqlUtility.validateRsqlFor(
|
||||
"ASSIGNEDDS.name == abcd and ASSIGNEDDS.version == 0123 and INSTALLEDDS.name == abcd and INSTALLEDDS.version == 0123",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager);
|
||||
RSQLUtility.validateRsqlFor(
|
||||
RsqlUtility.validateRsqlFor(
|
||||
"ATTRIBUTE.subkey1 == test and ATTRIBUTE.subkey2 == test and METADATA.metakey1 == abcd and METADATA.metavalue2 == asdfg",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager);
|
||||
RSQLUtility.validateRsqlFor(
|
||||
RsqlUtility.validateRsqlFor(
|
||||
"CREATEDAT =lt= ${NOW_TS} and LASTMODIFIEDAT =ge= ${OVERDUE_TS}",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager);
|
||||
RSQLUtility.validateRsqlFor(
|
||||
RsqlUtility.validateRsqlFor(
|
||||
"ATTRIBUTE.test.dot == test and ATTRIBUTE.subkey2 == test and METADATA.test.dot == abcd and METADATA.metavalue2 == asdfg",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager);
|
||||
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.isThrownBy(() -> RSQLUtility.validateRsqlFor(
|
||||
.isThrownBy(() -> RsqlUtility.validateRsqlFor(
|
||||
"wrongfield == abcd",
|
||||
TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager));
|
||||
}
|
||||
@@ -453,7 +452,7 @@ class RSQLTargetFieldTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private void assertRSQLQueryThrowsException(final String rsql) {
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.isThrownBy(() -> RSQLUtility.validateRsqlFor(
|
||||
.isThrownBy(() -> RsqlUtility.validateRsqlFor(
|
||||
rsql, TargetFields.class, JpaTarget.class, virtualPropertyReplacer, entityManager));
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import org.springframework.orm.jpa.vendor.Database;
|
||||
* Feature: Component Tests - Repository<br/>
|
||||
* Story: RSQL filter target filter query
|
||||
*/
|
||||
class RSQLTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest {
|
||||
class RsqlTargetFilterQueryFieldsTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
private TargetFilterQuery filter1;
|
||||
private TargetFilterQuery filter2;
|
||||
@@ -9,9 +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.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.G3;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G1;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.NONE;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
@@ -21,6 +21,7 @@ import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||
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.jpa.ql.utils.HawkbitQlToSql;
|
||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -37,10 +38,10 @@ import org.springframework.test.context.ContextConfiguration;
|
||||
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
||||
@Disabled("For manual run only, while playing around with RSQL to SQL")
|
||||
@SuppressWarnings("java:S2699") // java:S2699 - manual test, don't actually does assertions
|
||||
class RSQLToSQLTest {
|
||||
class RsqlToSqlTest {
|
||||
|
||||
private static final boolean FULL = Boolean.getBoolean("full");
|
||||
private RSQLToSQL rsqlToSQL;
|
||||
private HawkbitQlToSql rsqlToSQL;
|
||||
|
||||
@Test
|
||||
void printPG() {
|
||||
@@ -107,7 +108,7 @@ class RSQLToSQLTest {
|
||||
|
||||
@PersistenceContext
|
||||
private void setEntityManager(final EntityManager entityManager) {
|
||||
rsqlToSQL = new RSQLToSQL(entityManager);
|
||||
rsqlToSQL = new HawkbitQlToSql(entityManager);
|
||||
}
|
||||
|
||||
private <T, A extends Enum<A> & RsqlQueryField> void print(final Class<T> domainClass, final Class<A> fieldsClass, final String rsql) {
|
||||
@@ -46,7 +46,6 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
|
||||
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
|
||||
import org.eclipse.hawkbit.repository.model.helper.TenantConfigurationManagementHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
@@ -73,7 +72,7 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
@Disabled
|
||||
// TODO: fully document tests -> description for long text and reasonable
|
||||
// method name as short text
|
||||
class RSQLUtilityTest {
|
||||
class RsqlUtilityTest {
|
||||
|
||||
private static final TenantConfigurationValue<String> TEST_POLLING_TIME_INTERVAL =
|
||||
TenantConfigurationValue.<String> builder().value("00:05:00").build();
|
||||
@@ -109,7 +108,7 @@ class RSQLUtilityTest {
|
||||
@Test
|
||||
void wrongFieldThrowUnsupportedFieldException() {
|
||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) SoftwareModule.class);
|
||||
final Specification<Object> rsqlSpecification = RSQLUtility.buildRsqlSpecification("unknownField==abc", SoftwareModuleFields.class, null, testDb);
|
||||
final Specification<Object> rsqlSpecification = RsqlUtility.buildRsqlSpecification("unknownField==abc", SoftwareModuleFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterUnsupportedFieldException because of unknown RSQL field")
|
||||
.isThrownBy(() -> rsqlSpecification.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
@@ -118,25 +117,25 @@ class RSQLUtilityTest {
|
||||
@Test
|
||||
void wrongRsqlMapSyntaxThrowSyntaxException() {
|
||||
final Specification<Object> rsqlSpecification =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.ATTRIBUTE + "==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.ATTRIBUTE + "==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException for target attributes map, caused by wrong RSQL syntax (key was not present)")
|
||||
.isThrownBy(() -> rsqlSpecification.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
|
||||
final Specification<Object> rsqlSpecification2 =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.ATTRIBUTE + ".unknown.wrong==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.ATTRIBUTE + ".unknown.wrong==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException for target attributes map, caused by wrong RSQL syntax (key includes dots)")
|
||||
.isThrownBy(() -> rsqlSpecification2.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
|
||||
final Specification<Object> rsqlSpecification3 =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.METADATA + ".unknown.wrong==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.METADATA + ".unknown.wrong==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException for target metadata map, caused by wrong RSQL syntax (key includes dots)")
|
||||
.isThrownBy(() -> rsqlSpecification3.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
|
||||
final Specification<Object> rsqlSpecification4 =
|
||||
RSQLUtility.buildRsqlSpecification(DistributionSetFields.METADATA + "==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(DistributionSetFields.METADATA + "==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException for distribution set metadata map, caused by wrong RSQL syntax (key was not present)\"")
|
||||
.isThrownBy(() -> rsqlSpecification4.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
@@ -145,19 +144,19 @@ class RSQLUtilityTest {
|
||||
@Test
|
||||
void wrongRsqlSubEntitySyntaxThrowSyntaxException() {
|
||||
final Specification<Object> rsqlSpecification =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + "==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + "==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException because of wrong RSQL syntax")
|
||||
.isThrownBy(() -> rsqlSpecification.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
|
||||
final Specification<Object> rsqlSpecification2 =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + ".unknownField==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + ".unknownField==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException because of wrong RSQL syntax")
|
||||
.isThrownBy(() -> rsqlSpecification2.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
|
||||
final Specification<Object> rsqlSpecification3 =
|
||||
RSQLUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + ".unknownField.ToMuch==abc", TargetFields.class, null, testDb);
|
||||
RsqlUtility.buildRsqlSpecification(TargetFields.ASSIGNEDDS + ".unknownField.ToMuch==abc", TargetFields.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterSyntaxException because of wrong RSQL syntax")
|
||||
.isThrownBy(() -> rsqlSpecification3.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
@@ -176,7 +175,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.and(any(Predicate[].class))).thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -195,7 +194,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.upper(pathOfString(baseSoftwareModuleRootMock))).thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -217,7 +216,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.upper(pathOfString(baseSoftwareModuleRootMock))).thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -247,7 +246,7 @@ class RSQLUtilityTest {
|
||||
when(subqueryMock.where(any(Expression.class))).thenReturn(subqueryMock);
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -264,7 +263,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(mock(Predicate.class));
|
||||
when(criteriaBuilderMock.upper(pathOfString(baseSoftwareModuleRootMock))).thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.H2)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.H2)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -282,7 +281,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(mock(Predicate.class));
|
||||
when(criteriaBuilderMock.upper(pathOfString(baseSoftwareModuleRootMock))).thenReturn(pathOfString(baseSoftwareModuleRootMock));
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.H2)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.H2)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -304,7 +303,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class))).thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.SQL_SERVER)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, Database.SQL_SERVER)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -322,7 +321,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.<String> greaterThanOrEqualTo(any(Expression.class), any(String.class)))
|
||||
.thenReturn(mock(Predicate.class));
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, SoftwareModuleFields.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -339,7 +338,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.equal(any(Root.class), any(TestValueEnum.class))).thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, null, testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, null, testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -355,7 +354,7 @@ class RSQLUtilityTest {
|
||||
when(baseSoftwareModuleRootMock.getJavaType()).thenReturn((Class) TestValueEnum.class);
|
||||
when(criteriaBuilderMock.equal(any(Root.class), anyString())).thenReturn(mock(Predicate.class));
|
||||
|
||||
final Specification<Object> rsqlSpecification = RSQLUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, null, testDb);
|
||||
final Specification<Object> rsqlSpecification = RsqlUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, null, testDb);
|
||||
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class)
|
||||
.as("RSQLParameterUnsupportedFieldException for wrong enum value")
|
||||
.isThrownBy(() -> rsqlSpecification.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock));
|
||||
@@ -377,7 +376,7 @@ class RSQLUtilityTest {
|
||||
when(criteriaBuilderMock.lessThanOrEqualTo(any(Expression.class), eq(overduePropPlaceholder))).thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, setupMacroLookup(), testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, setupMacroLookup(), testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -401,7 +400,7 @@ class RSQLUtilityTest {
|
||||
.thenReturn(mock(Predicate.class));
|
||||
|
||||
// test
|
||||
RSQLUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, setupMacroLookup(), testDb)
|
||||
RsqlUtility.buildRsqlSpecification(correctRsql, TestFieldEnum.class, setupMacroLookup(), testDb)
|
||||
.toPredicate(baseSoftwareModuleRootMock, criteriaQueryMock, criteriaBuilderMock);
|
||||
|
||||
// verification
|
||||
@@ -1,235 +0,0 @@
|
||||
/**
|
||||
* 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.jpa.rsql.Node.Comparison.Operator.EQ;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.GT;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.GTE;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.IN;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.LIKE;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.LT;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.LTE;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.NE;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.NOT_IN;
|
||||
import static org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator.NOT_LIKE;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.Node;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.Node.Comparison.Operator;
|
||||
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlParser;
|
||||
|
||||
/**
|
||||
* Provides matching reference for if an object matches a {@link Node}.
|
||||
*/
|
||||
class ReferenceMatcher {
|
||||
|
||||
private final Node root;
|
||||
|
||||
private ReferenceMatcher(final Node root) {
|
||||
this.root = root;
|
||||
}
|
||||
|
||||
static ReferenceMatcher of(final Node root) {
|
||||
return new ReferenceMatcher(root);
|
||||
}
|
||||
|
||||
static ReferenceMatcher ofRsql(final String rsql) {
|
||||
return of(RsqlParser.parse(rsql));
|
||||
}
|
||||
|
||||
<T> boolean match(final T t) {
|
||||
return match(t, root);
|
||||
}
|
||||
|
||||
private static <T> boolean match(final T t, final Node node) {
|
||||
if (node instanceof Node.Comparison comparison) {
|
||||
final String[] split = comparison.getKey().split("\\.", 2);
|
||||
try {
|
||||
final Method fieldGetter = getGetter(t.getClass(), split[0]);
|
||||
fieldGetter.setAccessible(true);
|
||||
final Object fieldValue = fieldGetter.invoke(t);
|
||||
final Operator op = comparison.getOp();
|
||||
if (Map.class.isAssignableFrom(fieldGetter.getReturnType())) {
|
||||
if ((op == NE || op == NOT_IN || op == NOT_LIKE)
|
||||
&& (fieldValue == null || !((Map<?, ?>) fieldValue).containsKey(split[1]))) {
|
||||
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
|
||||
return false;
|
||||
}
|
||||
return compare(
|
||||
fieldValue == null ? null : ((Map<?, ?>) fieldValue).get(split[1]),
|
||||
op,
|
||||
map(
|
||||
comparison.getValue(),
|
||||
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[1]));
|
||||
} else if (Collection.class.isAssignableFrom(fieldGetter.getReturnType())) { // Set / List
|
||||
final Object value;
|
||||
final BiFunction<Object, Operator, Boolean> compare;
|
||||
if (split.length == 1) {
|
||||
value = map(comparison.getValue(), fieldGetter.getReturnType());
|
||||
compare = (e, operator) -> compare(e, operator, value);
|
||||
} else {
|
||||
final Method valueGetter = getGetter(
|
||||
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[0], split[1]);
|
||||
value = map(comparison.getValue(), valueGetter.getReturnType());
|
||||
compare = (e, operator) -> {
|
||||
try {
|
||||
return compare(map(e == null ? null : valueGetter.invoke(e), valueGetter.getReturnType()), operator, value);
|
||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
final Collection<?> set = (Collection<?>) fieldValue;
|
||||
return switch (op) {
|
||||
case EQ, GT, GTE, LT, LTE, IN, LIKE -> set == null
|
||||
? false
|
||||
: set.stream().anyMatch(e -> compare.apply(e, op));
|
||||
case NE, NOT_IN, NOT_LIKE -> set == null
|
||||
? true
|
||||
: set.stream().noneMatch(e -> compare.apply(e, op == NE ? EQ : op == NOT_IN ? IN : LIKE));
|
||||
};
|
||||
} else {
|
||||
if (split.length == 1) {
|
||||
return compare(fieldValue, op, map(comparison.getValue(), fieldGetter.getReturnType()));
|
||||
} else {
|
||||
if (split[1].contains(".")) {
|
||||
// nested field access
|
||||
final String[] nestedSplit = split[1].split("\\.", 2);
|
||||
final Method nestedFieldGetter = getGetter(fieldGetter.getReturnType(), nestedSplit[0]);
|
||||
nestedFieldGetter.setAccessible(true);
|
||||
final Method valueGetter = getGetter(nestedFieldGetter.getReturnType(), nestedSplit[1]);
|
||||
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.invoke(fieldValue);
|
||||
return compare(
|
||||
nestedFieldValue == null ? null : valueGetter.invoke(nestedFieldValue),
|
||||
op,
|
||||
map(comparison.getValue(), valueGetter.getReturnType()));
|
||||
} else {
|
||||
final Method valueGetter = getGetter(fieldGetter.getReturnType(), split[1]);
|
||||
return compare(fieldValue == null ? null : valueGetter.invoke(fieldValue), op,
|
||||
map(comparison.getValue(), valueGetter.getReturnType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
} else if (node instanceof Node.Logical logical) {
|
||||
return switch (logical.getOp()) {
|
||||
case AND -> logical.getChildren().stream().allMatch(child -> match(t, child));
|
||||
case OR -> logical.getChildren().stream().anyMatch(child -> match(t, child));
|
||||
};
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported node type: " + node.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> Method getGetter(final Class<T> t, final String fieldName) throws NoSuchMethodException {
|
||||
final String getterLowercase = "get" + fieldName.toLowerCase();
|
||||
return Arrays.stream(t.getMethods())
|
||||
.filter(method -> getterLowercase.equals(method.getName().toLowerCase()))
|
||||
.findFirst()
|
||||
.map(method -> {
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
}).orElseThrow(() -> new NoSuchMethodException("No getter found for field: " + fieldName + " in class: " + t.getName()));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private static Object map(final Object value, final Class<?> type) {
|
||||
if (value instanceof Collection<?> collection) { // in / out
|
||||
return collection.stream().map(e -> map(e, type)).toList();
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
} else if (type.isInstance(value)) {
|
||||
return value;
|
||||
} else if (type.isEnum()) {
|
||||
return Enum.valueOf((Class<Enum>) type, value.toString());
|
||||
} else if (type == Boolean.class || type == boolean.class) {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
} else if (type == Integer.class || type == int.class) {
|
||||
return Integer.parseInt(value.toString());
|
||||
} else if (type == Long.class || type == long.class) {
|
||||
return Long.parseLong(value.toString());
|
||||
} else if (type == Float.class || type == float.class) {
|
||||
return Float.parseFloat(value.toString());
|
||||
} else if (type == Double.class || type == double.class) {
|
||||
return Double.parseDouble(value.toString());
|
||||
} else if (type == String.class) {
|
||||
return String.valueOf(value);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported type: " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean compare(final Object o1, final Operator op, final Object o2) {
|
||||
if ((o1 == null || o2 == null) && // null is not comparable!
|
||||
(op == GT || op == GTE || op == LT || op == LTE)) {
|
||||
return false;
|
||||
}
|
||||
return switch (op) {
|
||||
case EQ -> Objects.equals(o1, o2);
|
||||
case NE -> !Objects.equals(o1, o2);
|
||||
case GT -> compare(o1, o2) > 0;
|
||||
case GTE -> compare(o1, o2) >= 0;
|
||||
case LT -> compare(o1, o2) < 0;
|
||||
case LTE -> compare(o1, o2) <= 0;
|
||||
case IN -> in(o1, o2);
|
||||
case NOT_IN -> !in(o1, o2);
|
||||
case LIKE -> like(o2, o1);
|
||||
case NOT_LIKE -> !like(o2, o1);
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static int compare(final Object o1, final Object o2) {
|
||||
return toComparable(o1).compareTo(toComparable(o2));
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Comparable toComparable(final Object o) {
|
||||
if (o instanceof Comparable<?> comparable) {
|
||||
return comparable;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Can't cast " + o.getClass() + " to Comparable");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean in(final Object o, final Object elementOrCollection) {
|
||||
if (elementOrCollection instanceof Collection<?> collection) {
|
||||
return collection.contains(o);
|
||||
} else {
|
||||
return Objects.equals(o, elementOrCollection);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean like(final Object pattern, final Object value) {
|
||||
if (pattern instanceof String patternStr) {
|
||||
if (value instanceof String valueStr) {
|
||||
return valueStr.matches(patternStr.replace("\\*", "$").replace("*", ".*").replace("$", "\\*"));
|
||||
} else if (value == null) {
|
||||
return false; // null value cannot match any pattern
|
||||
} else {
|
||||
throw new IllegalArgumentException("LIKE value must be String. Found: " + value.getClass());
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("LIKE pattern must be String. Found: " + (pattern == null ? null : pattern.getClass()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* 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 java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import jakarta.persistence.CollectionTable;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.ElementCollection;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.JoinTable;
|
||||
import jakarta.persistence.ManyToMany;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.MapKeyColumn;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
class Root {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// singular attributes
|
||||
// basic
|
||||
private String strValue;
|
||||
private int intValue;
|
||||
// entity
|
||||
@ManyToOne
|
||||
private Sub subEntity;
|
||||
// set searchable by key
|
||||
@ManyToMany(targetEntity = Sub.class)
|
||||
@JoinTable(
|
||||
name = "subs",
|
||||
joinColumns = { @JoinColumn(name = "root") },
|
||||
inverseJoinColumns = { @JoinColumn(name = "subs") })
|
||||
private Set<Sub> subSet;
|
||||
// standard map
|
||||
@ElementCollection
|
||||
@CollectionTable(
|
||||
name = "map",
|
||||
joinColumns = { @JoinColumn(name = "root") })
|
||||
@MapKeyColumn(name = "map_key", length = 128)
|
||||
@Column(name = "map_value", length = 128)
|
||||
private Map<String, String> subMap;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 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 org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface RootRepository extends CrudRepository<Root, Long>, JpaSpecificationExecutor<Root> {}
|
||||
@@ -1,128 +0,0 @@
|
||||
/**
|
||||
* 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<RootField, Root> 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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singularEntitySubSubAttributeG1() {
|
||||
runWithRsqlToSpecBuilder(super::singularEntitySubSubAttribute, LEGACY_G1);
|
||||
}
|
||||
@Override
|
||||
@Test
|
||||
void singularEntitySubSubAttribute() {
|
||||
runWithRsqlToSpecBuilder(super::singularEntitySubSubAttribute, LEGACY_G2);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Specification<Root> getSpecification(final String rsql) {
|
||||
return builder.specification(rsql);
|
||||
}
|
||||
|
||||
@Getter
|
||||
private enum RootField implements RsqlQueryField {
|
||||
|
||||
INTVALUE("intValue"),
|
||||
STRVALUE("strValue"),
|
||||
SUBENTITY("subEntity", "strValue", "intValue", "subSub"),
|
||||
SUBSET("subSet", "strValue", "intValue"),
|
||||
SUBMAP("subMap");
|
||||
|
||||
private final String jpaEntityFieldName;
|
||||
private final List<String> subEntityAttributes;
|
||||
|
||||
RootField(final String jpaEntityFieldName, final String... subFields) {
|
||||
this.jpaEntityFieldName = jpaEntityFieldName;
|
||||
this.subEntityAttributes = Arrays.asList(subFields);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMap() {
|
||||
return this == SUBMAP;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,500 +0,0 @@
|
||||
/**
|
||||
* 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.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 static org.eclipse.hawkbit.repository.rsql.RsqlConfigHolder.RsqlToSpecBuilder.LEGACY_G2;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
|
||||
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 = {
|
||||
"spring.jpa.database=H2",
|
||||
"logging.level.org.eclipse.hawkbit.repository.jpa.rsql=DEBUG"
|
||||
}, excludeAutoConfiguration = { FlywayAutoConfiguration.class })
|
||||
@EnableAutoConfiguration
|
||||
@Slf4j
|
||||
class SpecificationBuilderTest {
|
||||
|
||||
@Autowired
|
||||
private SubSubRepository subSubRepository;
|
||||
@Autowired
|
||||
private SubRepository subRepository;
|
||||
@Autowired
|
||||
private RootRepository rootRepository;
|
||||
@Autowired
|
||||
private EntityManager entityManager;
|
||||
|
||||
private final SpecificationBuilder<Root> builder = new SpecificationBuilder<>(null, false, Database.H2);
|
||||
|
||||
@Test
|
||||
void singularStringAttribute() {
|
||||
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(root1, root2);
|
||||
assertThat(filter("strValue==nostr")).isEmpty();
|
||||
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);
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1) {
|
||||
assertThat(filter("strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
}
|
||||
assertThat(filter("strValue<rooty")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("strValue<=rooty")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("strValue>rootx")).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(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==*")).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 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(root1, root2);
|
||||
assertThat(filter("intValue==2")).isEmpty();
|
||||
assertThat(filter("intValue!=0")).hasSize(2).containsExactlyInAnyOrder(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(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 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(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!=nostr")).hasSize(5);
|
||||
assertThat(filter("subEntity.strValue<suby")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.strValue<=suby")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.strValue>subx")).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);
|
||||
// wildcard, like
|
||||
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==*")).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(root1, root2);
|
||||
assertThat(filter("subEntity.intValue==2")).isEmpty();
|
||||
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(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 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(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(root1, root4, root5);
|
||||
assertThat(filter("subSet.strValue==nostr")).isEmpty();
|
||||
assertThat(filter("subSet.strValue!=subx")).hasSize(3).containsExactlyInAnyOrder(root2, root3, root6);
|
||||
assertThat(filter("subSet.strValue!=nostr")).hasSize(6);
|
||||
assertThat(filter("subSet.strValue<suby")).hasSize(3).containsExactlyInAnyOrder(root1, root4, root5);
|
||||
assertThat(filter("subSet.strValue<=suby")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5);
|
||||
assertThat(filter("subSet.strValue>subx")).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);
|
||||
// wildcard, like
|
||||
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==*")).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(root1, root3, root4, root5);
|
||||
assertThat(filter("subSet.intValue==2")).isEmpty();
|
||||
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(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 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(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(root1, root2, root3, root4, root5);
|
||||
assertThat(filter("subMap.x<rooty")).hasSize(3).containsExactlyInAnyOrder(root1, root2, root5);
|
||||
assertThat(filter("subMap.x<=rooty")).hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5);
|
||||
assertThat(filter("subMap.x>rootx")).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(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==*")).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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void singularEntitySubSubAttribute() {
|
||||
final SubSub subSub1 = subSubRepository.save(new SubSub().setStrValue("subx").setIntValue(0));
|
||||
final SubSub subSub2 = subSubRepository.save(new SubSub().setStrValue("suby").setIntValue(1));
|
||||
final Sub sub1 = subRepository.save(new Sub().setSubSub(subSub1));
|
||||
final Sub sub2 = subRepository.save(new Sub().setSubSub(subSub2));
|
||||
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.subSub.strValue==subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue==nostr")).isEmpty();
|
||||
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it is
|
||||
assertThat(filter("subEntity.subSub.strValue!=subx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.strValue!=nostr")).hasSize(5);
|
||||
assertThat(filter("subEntity.subSub.strValue<suby")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue<=suby")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue>subx")).hasSize(2).containsExactlyInAnyOrder(root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue>=subx")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue=in=subx")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue=in=(subx, suby)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue=in=(subZ, subT)")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.strValue=out=subx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.strValue=out=(subx, suby)")).hasSize(1).containsExactlyInAnyOrder(root5);
|
||||
// wildcard, like
|
||||
assertThat(filter("subEntity.subSub.strValue==sub*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue!=sub*")).hasSize(1).containsExactlyInAnyOrder(root5);
|
||||
assertThat(filter("subEntity.subSub.strValue!=*bx")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.strValue==*")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.strValue!=*")).hasSize(1).containsExactlyInAnyOrder(root5);
|
||||
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1) {
|
||||
// null checks
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G2) {
|
||||
assertThat(filter("subEntity.subSub.strValue=is=null")).hasSize(1).containsExactlyInAnyOrder(root5);
|
||||
}
|
||||
assertThat(filter("subEntity.subSub.strValue=not=null")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
}
|
||||
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx and subEntity.subSub.strValue==suby")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx and subEntity.subSub.strValue!=subx")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx and subEntity.subSub.strValue==subx"))
|
||||
.hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue==suby"))
|
||||
.hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) {
|
||||
// G1 doesn't get null entity in not, and doesn't support =is= and =not=
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue!=subx"))
|
||||
.hasSize(5).containsExactlyInAnyOrder(root1, root2, root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.strValue==*bx or subEntity.subSub.strValue=is=null"))
|
||||
.hasSize(3).containsExactlyInAnyOrder(root1, root2, root5);
|
||||
}
|
||||
|
||||
// by sub entity int
|
||||
assertThat(filter("subEntity.subSub.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.intValue==2")).isEmpty();
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) {
|
||||
// G1 doesn't get null entity in not
|
||||
assertThat(filter("subEntity.subSub.intValue!=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.intValue!=2")).hasSize(5);
|
||||
}
|
||||
assertThat(filter("subEntity.subSub.intValue<1")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.intValue<=1")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.intValue>0")).hasSize(2).containsExactlyInAnyOrder(root3, root4);
|
||||
assertThat(filter("subEntity.subSub.intValue>=0")).hasSize(4);
|
||||
assertThat(filter("subEntity.subSub.intValue=in=0")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.intValue=in=(0, 1)")).hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
assertThat(filter("subEntity.subSub.intValue=in=(2, 3)")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.intValue=out=0")).hasSize(3).containsExactlyInAnyOrder(root3, root4, root5);
|
||||
assertThat(filter("subEntity.subSub.intValue=out=(0, 1)")).hasSize(1).containsExactlyInAnyOrder(root5);
|
||||
|
||||
assertThat(filter("subEntity.subSub.intValue==0 and subEntity.subSub.intValue==1")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.intValue==0 and subEntity.subSub.intValue!=1")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.intValue==0 and subEntity.subSub.intValue==0")).hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.intValue==0 or subEntity.subSub.intValue==1")).hasSize(4)
|
||||
.containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) {
|
||||
// G1 doesn't get null entity in not
|
||||
assertThat(filter("subEntity.subSub.intValue==0 or subEntity.subSub.intValue!=1"))
|
||||
.hasSize(3).containsExactlyInAnyOrder(root1, root2, root5);
|
||||
}
|
||||
|
||||
assertThat(filter("subEntity.subSub.strValue==subx and subEntity.subSub.intValue==1")).isEmpty();
|
||||
assertThat(filter("subEntity.subSub.strValue==subx and subEntity.subSub.intValue!=1")).hasSize(2)
|
||||
.containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue==subx and subEntity.subSub.intValue==0"))
|
||||
.hasSize(2).containsExactlyInAnyOrder(root1, root2);
|
||||
assertThat(filter("subEntity.subSub.strValue==subx or subEntity.subSub.intValue==1"))
|
||||
.hasSize(4).containsExactlyInAnyOrder(root1, root2, root3, root4);
|
||||
if (getRsqlToSpecBuilder() != LEGACY_G1 && getRsqlToSpecBuilder() != LEGACY_G2) {
|
||||
// G1 doesn't get null entity in not
|
||||
assertThat(filter("subEntity.subSub.strValue==subx or subEntity.subSub.intValue!=1"))
|
||||
.hasSize(3).containsExactlyInAnyOrder(root1, root2, root5);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Root> filter(final String rsql) {
|
||||
// reference / auto filter (using elements and reflection)
|
||||
final ReferenceMatcher matcher = ReferenceMatcher.ofRsql(rsql);
|
||||
final List<Root> refResult = StreamSupport.stream(rootRepository.findAll().spliterator(), false).filter(matcher::match).toList();
|
||||
final List<Root> result = rootRepository.findAll(getSpecification(rsql));
|
||||
// auto check with reference result
|
||||
try {
|
||||
assertThat(result).containsExactlyInAnyOrder(refResult.toArray(Root[]::new));
|
||||
} catch (final AssertionError e) {
|
||||
log.error(
|
||||
"Fail to get expected result for RSQL: {} with SQL query: {}",
|
||||
rsql, new RSQLToSQL(entityManager).toSQL(Root.class, null, rsql, G3),
|
||||
e);
|
||||
throw e;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected Specification<Root> getSpecification(final String rsql) {
|
||||
return builder.specification(RsqlParser.parse(rsql));
|
||||
}
|
||||
|
||||
private static RsqlConfigHolder.RsqlToSpecBuilder getRsqlToSpecBuilder() {
|
||||
return RsqlConfigHolder.getInstance().getRsqlToSpecBuilder();
|
||||
}
|
||||
|
||||
@SpringBootConfiguration
|
||||
static class Config {}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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 jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
class Sub {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// singular attributes
|
||||
// basic
|
||||
private String strValue;
|
||||
private int intValue;
|
||||
// entity
|
||||
@ManyToOne
|
||||
private SubSub subSub;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 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 org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SubRepository extends CrudRepository<Sub, Long>, JpaSpecificationExecutor<Sub> {}
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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 jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Entity
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
class SubSub {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
// singular attributes
|
||||
// basic
|
||||
private String strValue;
|
||||
private int intValue;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 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 org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SubSubRepository extends CrudRepository<SubSub, Long>, JpaSpecificationExecutor<SubSub> {}
|
||||
Reference in New Issue
Block a user