Skip to content

Commit

Permalink
Fix pageable queries with dynamic entity name (#3245)
Browse files Browse the repository at this point in the history
* Fix schema generator when mapped entity contains property placeholder

* Fix tests

* Fix tests and move to jdbc-example-java

* Try to improve test coverage

* Fix pageable query for entity with dynamic name

* Add test with find IN

* Revert unwanted change

* Test with IN and property expression value

* Added test with count as well

* More examples with count

* Trigger build
  • Loading branch information
radovanradic authored Dec 5, 2024
1 parent 06c93d2 commit 13e8122
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
*/
package io.micronaut.data.runtime.query.internal;

import io.micronaut.context.ApplicationContextProvider;
import io.micronaut.context.env.Environment;
import io.micronaut.context.env.PropertyPlaceholderResolver;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.AnnotationValue;
import io.micronaut.core.annotation.Internal;
Expand All @@ -39,13 +42,15 @@

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import static io.micronaut.data.intercept.annotation.DataMethod.META_MEMBER_LIMIT;
Expand Down Expand Up @@ -88,6 +93,7 @@ public final class DefaultStoredQuery<E, RT> extends DefaultStoredDataOperation<
private final Map<String, AnnotationValue<?>> parameterExpressions;
private final int limit;
private final int offset;
private final Function<Object, Object> stringsEnvResolverValueMapper;

/**
* The default constructor.
Expand Down Expand Up @@ -132,6 +138,13 @@ public DefaultStoredQuery(
HintsCapableRepository repositoryOperations) {
super(method);

if (repositoryOperations instanceof ApplicationContextProvider applicationContextProvider) {
Environment environment = applicationContextProvider.getApplicationContext().getEnvironment();
stringsEnvResolverValueMapper = createEnvResolverValueMapper(environment);
} else {
stringsEnvResolverValueMapper = null;
}

this.rootEntity = getRequiredRootEntity(method);
this.annotationMetadata = method.getAnnotationMetadata();
this.isProcedure = dataMethodQuery.isTrue(DataMethodQuery.META_MEMBER_PROCEDURE);
Expand Down Expand Up @@ -183,7 +196,7 @@ public DefaultStoredQuery(
this.query = rawQueryString.orElse(query);
}
this.resultDataType = dataMethodQuery.enumValue(DataMethodQuery.META_MEMBER_RESULT_DATA_TYPE, DataType.class).orElse(DataType.OBJECT);
this.queryParts = dataMethodQuery.stringValues(DataMethodQuery.META_MEMBER_EXPANDABLE_QUERY);
this.queryParts = getQueryParts(dataMethodQuery, DataMethodQuery.META_MEMBER_EXPANDABLE_QUERY);
//noinspection unchecked
this.resultType = dataMethodQuery.classValue(DataMethodQuery.META_MEMBER_RESULT_TYPE)
.map(type -> (Class<RT>) ReflectionUtils.getWrapperType(type))
Expand Down Expand Up @@ -480,4 +493,29 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(resultType, method);
}

private String[] getQueryParts(@NonNull AnnotationValue annotationValue, @NonNull String member) {
if (stringsEnvResolverValueMapper != null) {
return annotationValue.stringValues(member, stringsEnvResolverValueMapper);
}
return annotationValue.stringValues(member);
}

private static Function<Object, Object> createEnvResolverValueMapper(Environment environment) {
return o -> {
PropertyPlaceholderResolver resolver = environment.getPlaceholderResolver();
if (o instanceof String[] values) {
String[] resolvedValues = Arrays.copyOf(values, values.length);
for (int i = 0; i < values.length; i++) {
String value = values[i];
if (value.contains(resolver.getPrefix())) {
value = resolver.resolveRequiredPlaceholders(value);
}
resolvedValues[i] = value;
}
return resolvedValues;
}
return o;
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
package example;

import io.micronaut.data.annotation.Query;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.Slice;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;

import java.util.List;
import java.util.Set;

@JdbcRepository(dialect = Dialect.H2)
public interface CustomEntityRepository extends CrudRepository<CustomEntity, Long> {
Page<CustomEntity> findAll(Pageable pageable);

Page<CustomEntity> findByNameIn(List<String> names, Pageable pageable);

@Query(value = "SELECT * FROM ${entity.prefix}entity WHERE name IN ('${entity.name}')", nativeQuery = true)
List<CustomEntity> findDataByEnvPropertyValue();

@Query(value = "SELECT id, '${entity.name}' AS name FROM ${entity.prefix}entity WHERE id IN (:id)", nativeQuery = true
)
List<CustomEntity> findDataById(List<Long> id);

@Query(value = "SELECT COUNT(*) FROM ${entity.prefix}entity WHERE name IN ('${entity.name}')", nativeQuery = true)
long countDataByEnvPropertyValue();

long countByIdIn(List<Long> ids);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ datasources:
dialect: H2
entity:
prefix: demo_
name: Entity1
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package example;

import io.micronaut.context.annotation.Value;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.List;

@MicronautTest
class CustomEntityRepositorySpec {

@Inject
CustomEntityRepository repository;

@Value("${entity.name}")
String entityName;

@Test
void testSaveAndFind() {
CustomEntity entity = repository.save(new CustomEntity(null, "Entity1"));
Expand All @@ -19,6 +27,42 @@ void testSaveAndFind() {
Assertions.assertEquals(entity.name(), found.name());
Assertions.assertEquals(1, repository.count());
Assertions.assertFalse(repository.findAll().isEmpty());

CustomEntity entity2 = repository.save(new CustomEntity(null, "Entity2"));
CustomEntity entity3 = repository.save(new CustomEntity(null, "Entity3"));

List<String> allNames = List.of("Entity1", "Entity2", "Entity3");
List<Long> allIds = List.of(entity.id(), entity2.id(), entity3.id());

Page<CustomEntity> page = repository.findAll(Pageable.from(0, 2));
Assertions.assertEquals(2, page.getSize());
Assertions.assertEquals(3, page.getTotalSize());

page = repository.findByNameIn(allNames, Pageable.from(0, 2));
Assertions.assertEquals(2, page.getSize());
Assertions.assertEquals(2, page.getTotalPages());
Assertions.assertEquals(3, page.getTotalSize());

Assertions.assertEquals(3, repository.countByIdIn(allIds));

page = repository.findByNameIn(List.of("Entity1", "Entity2"),
Pageable.from(0, 2));
Assertions.assertEquals(2, page.getSize());
Assertions.assertEquals(1, page.getTotalPages());
Assertions.assertEquals(2, page.getTotalSize());

List<CustomEntity> customEntities = repository.findDataByEnvPropertyValue();
Assertions.assertEquals(1, customEntities.size());
Assertions.assertEquals(entityName, customEntities.get(0).name());

Assertions.assertEquals(1, repository.countDataByEnvPropertyValue());

customEntities = repository.findDataById(allIds);
Assertions.assertEquals(3, customEntities.size());
for (CustomEntity customEntity : customEntities) {
Assertions.assertEquals(entityName, customEntity.name());
}

repository.deleteAll();
}
}

0 comments on commit 13e8122

Please sign in to comment.