Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider supporting record projections #29

Merged
merged 1 commit into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
*/
package org.springframework.data.reindexer.repository.query;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -27,10 +26,14 @@
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import ru.rt.restream.reindexer.AggregationResult;

import org.springframework.data.mapping.PreferredConstructor;
import org.springframework.data.mapping.model.PreferredConstructorDiscoverer;
import org.springframework.data.reindexer.repository.support.TransactionalNamespace;
import ru.rt.restream.reindexer.FieldType;
import ru.rt.restream.reindexer.Namespace;
Expand All @@ -46,6 +49,7 @@
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.repository.query.ParametersParameterAccessor;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.data.repository.query.ReturnedType;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
import org.springframework.data.repository.query.parser.PartTree.OrPart;
Expand All @@ -58,6 +62,8 @@
*/
public class ReindexerRepositoryQuery implements RepositoryQuery {

private final Map<Class<?>, Constructor<?>> preferredConstructors = new ConcurrentHashMap<>();

private final ReindexerQueryMethod queryMethod;

private final Namespace<?> namespace;
Expand All @@ -66,8 +72,6 @@ public class ReindexerRepositoryQuery implements RepositoryQuery {

private final Map<String, ReindexerIndex> indexes;

private final String[] selectFields;

/**
* Creates an instance.
*
Expand All @@ -85,31 +89,23 @@ public ReindexerRepositoryQuery(ReindexerQueryMethod queryMethod, ReindexerEntit
for (ReindexerIndex index : namespace.getIndexes()) {
this.indexes.put(index.getName(), index);
}
this.selectFields = getSelectFields();
}

private String[] getSelectFields() {
if (this.queryMethod.getDomainClass() == this.queryMethod.getReturnedObjectType()
|| this.queryMethod.getParameters().hasDynamicProjection()) {
return new String[0];
}
return getSelectFields(this.queryMethod.getReturnedObjectType());
}

@Override
public Object execute(Object[] parameters) {
Query<?> query = createQuery(parameters);
ReturnedType projectionType = getProjectionType(parameters);
Query<?> query = createQuery(projectionType, parameters);
if (this.queryMethod.isCollectionQuery()) {
return toCollection(query, parameters);
return toCollection(query, projectionType);
}
if (this.queryMethod.isStreamQuery()) {
return toStream(query, parameters);
return toStream(query, projectionType);
}
if (this.queryMethod.isIteratorQuery()) {
return new ProjectingResultIterator(query, parameters);
return new ProjectingResultIterator(query, projectionType);
}
if (this.queryMethod.isQueryForEntity()) {
Object entity = toEntity(query, parameters);
Object entity = toEntity(query, projectionType);
Assert.state(entity != null, "Exactly one item expected, but there is zero");
return entity;
}
Expand All @@ -123,13 +119,25 @@ public Object execute(Object[] parameters) {
query.delete();
return null;
}
return Optional.ofNullable(toEntity(query, parameters));
return Optional.ofNullable(toEntity(query, projectionType));
}

private Query<?> createQuery(Object[] parameters) {
private ReturnedType getProjectionType(Object[] parameters) {
if (this.queryMethod.getDomainClass() == this.queryMethod.getReturnedObjectType()) {
return null;
}
Class<?> type = this.queryMethod.getParameters().hasDynamicProjection()
? (Class<?>) parameters[this.queryMethod.getParameters().getDynamicProjectionIndex()]
: this.queryMethod.getReturnedObjectType();
return ReturnedType.of(type, this.queryMethod.getDomainClass(), this.queryMethod.getFactory());
}

private Query<?> createQuery(ReturnedType projectionType, Object[] parameters) {
ParametersParameterAccessor accessor =
new ParametersParameterAccessor(this.queryMethod.getParameters(), parameters);
Query<?> base = this.namespace.query().select(getSelectFields(parameters));
String[] selectFields = (projectionType != null) ? projectionType.getInputProperties().toArray(String[]::new)
: new String[0];
Query<?> base = this.namespace.query().select(selectFields);
Iterator<Object> iterator = accessor.iterator();
for (OrPart node : this.tree) {
Iterator<Part> parts = node.iterator();
Expand All @@ -149,34 +157,6 @@ private Query<?> createQuery(Object[] parameters) {
return base;
}

private String[] getSelectFields(Object[] parameters) {
if (this.queryMethod.getParameters().hasDynamicProjection()) {
Class<?> type = (Class<?>) parameters[this.queryMethod.getParameters().getDynamicProjectionIndex()];
return getSelectFields(type);
}
return this.selectFields;
}

private String[] getSelectFields(Class<?> type) {
if (type.isInterface()) {
List<PropertyDescriptor> inputProperties = this.queryMethod.getFactory()
.getProjectionInformation(type).getInputProperties();
String[] result = new String[inputProperties.size()];
for (int i = 0; i < result.length; i++) {
result[i] = inputProperties.get(i).getName();
}
return result;
}
else {
List<Field> inheritedFields = BeanPropertyUtils.getInheritedFields(type);
String[] result = new String[inheritedFields.size()];
for (int i = 0; i < result.length; i++) {
result[i] = inheritedFields.get(i).getName();
}
return result;
}
}

private Query<?> where(Part part, Query<?> criteria, Iterator<Object> parameters) {
String indexName = part.getProperty().toDotPath();
switch (part.getType()) {
Expand Down Expand Up @@ -240,8 +220,8 @@ private Object getParameterValue(String indexName, Object value) {
return value;
}

private Collection<?> toCollection(Query<?> query, Object[] parameters) {
try (ResultIterator<?> iterator = new ProjectingResultIterator(query, parameters)) {
private Collection<?> toCollection(Query<?> query, ReturnedType projectionType) {
try (ResultIterator<?> iterator = new ProjectingResultIterator(query, projectionType)) {
Collection<Object> result = CollectionFactory.createCollection(this.queryMethod.getReturnType(),
this.queryMethod.getReturnedObjectType(), (int) iterator.size());
while (iterator.hasNext()) {
Expand All @@ -251,14 +231,14 @@ private Collection<?> toCollection(Query<?> query, Object[] parameters) {
}
}

private Stream<?> toStream(Query<?> query, Object[] parameters) {
ResultIterator<?> iterator = new ProjectingResultIterator(query, parameters);
private Stream<?> toStream(Query<?> query, ReturnedType projectionType) {
ResultIterator<?> iterator = new ProjectingResultIterator(query, projectionType);
Spliterator<?> spliterator = Spliterators.spliterator(iterator, iterator.size(), Spliterator.NONNULL);
return StreamSupport.stream(spliterator, false).onClose(iterator::close);
}

private Object toEntity(Query<?> query, Object[] parameters) {
try (ResultIterator<?> iterator = new ProjectingResultIterator(query, parameters)) {
private Object toEntity(Query<?> query, ReturnedType projectionType) {
try (ResultIterator<?> iterator = new ProjectingResultIterator(query, projectionType)) {
Object item = null;
if (iterator.hasNext()) {
item = iterator.next();
Expand All @@ -279,28 +259,13 @@ public ReindexerQueryMethod getQueryMethod() {

private final class ProjectingResultIterator implements ResultIterator<Object> {

private final Object[] parameters;

private final ResultIterator<?> delegate;

private ProjectingResultIterator(Query<?> query, Object[] parameters) {
this.parameters = parameters;
this.delegate = query.execute(determineReturnType());
}
private final ReturnedType projectionType;

private Class<?> determineReturnType() {
if (ReindexerRepositoryQuery.this.queryMethod.getParameters().hasDynamicProjection()) {
Class<?> type = (Class<?>) this.parameters[ReindexerRepositoryQuery.this.queryMethod.getParameters().getDynamicProjectionIndex()];
if (type.isInterface()) {
return ReindexerRepositoryQuery.this.queryMethod.getDomainClass();
}
return type;
}
if (ReindexerRepositoryQuery.this.queryMethod.getDomainClass() != ReindexerRepositoryQuery.this.queryMethod.getReturnedObjectType()
&& ReindexerRepositoryQuery.this.queryMethod.getReturnedObjectType().isInterface()) {
return ReindexerRepositoryQuery.this.queryMethod.getDomainClass();
}
return ReindexerRepositoryQuery.this.queryMethod.getReturnedObjectType();
private ProjectingResultIterator(Query<?> query, ReturnedType projectionType) {
this.delegate = query.execute();
this.projectionType = projectionType;
}

@Override
Expand Down Expand Up @@ -331,15 +296,26 @@ public boolean hasNext() {
@Override
public Object next() {
Object item = this.delegate.next();
if (ReindexerRepositoryQuery.this.queryMethod.getParameters().hasDynamicProjection()) {
Class<?> type = (Class<?>) this.parameters[ReindexerRepositoryQuery.this.queryMethod.getParameters().getDynamicProjectionIndex()];
if (type.isInterface()) {
return ReindexerRepositoryQuery.this.queryMethod.getFactory().createProjection(type, item);
if (this.projectionType != null) {
if (this.projectionType.getReturnedType().isInterface()) {
return ReindexerRepositoryQuery.this.queryMethod.getFactory().createProjection(this.projectionType.getReturnedType(), item);
}
List<String> properties = this.projectionType.getInputProperties();
Object[] values = new Object[properties.size()];
for (int i = 0; i < properties.size(); i++) {
values[i] = BeanPropertyUtils.getProperty(item, properties.get(i));
}
Constructor<?> constructor = ReindexerRepositoryQuery.this.preferredConstructors.computeIfAbsent(this.projectionType.getReturnedType(), (type) -> {
PreferredConstructor<?, ?> preferredConstructor = PreferredConstructorDiscoverer.discover(type);
Assert.state(preferredConstructor != null, () -> "No preferred constructor found for " + type);
return preferredConstructor.getConstructor();
});
try {
return constructor.newInstance(values);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
else if (ReindexerRepositoryQuery.this.queryMethod.getReturnedObjectType().isInterface()) {
return ReindexerRepositoryQuery.this.queryMethod.getFactory()
.createProjection(ReindexerRepositoryQuery.this.queryMethod.getReturnedObjectType(), item);
}
return item;
}
Expand Down
Loading
Loading