Skip to content

Commit e266d0d

Browse files
committed
spring-projects#282 - Starting point for PartTree-based query derivation
1 parent e56f126 commit e266d0d

File tree

2 files changed

+168
-0
lines changed

2 files changed

+168
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package org.springframework.data.r2dbc.repository.query;
2+
3+
import org.springframework.data.domain.Sort;
4+
import org.springframework.data.r2dbc.convert.R2dbcConverter;
5+
import org.springframework.data.r2dbc.core.DatabaseClient;
6+
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
7+
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
8+
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
9+
import org.springframework.data.relational.repository.query.RelationalParameters;
10+
import org.springframework.data.repository.query.parser.Part;
11+
import org.springframework.data.repository.query.parser.PartTree;
12+
import org.springframework.data.util.Streamable;
13+
import org.springframework.util.Assert;
14+
15+
/**
16+
* An {@link AbstractR2dbcQuery} implementation based on a {@link PartTree}.
17+
* <p>
18+
* This class is an adapted version of {@code org.springframework.data.jpa.repository.query.PartTreeJpaQuery} from
19+
* Spring Data JPA project.
20+
*
21+
* @author Roman Chigvintsev
22+
*/
23+
public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
24+
private final ReactiveDataAccessStrategy dataAccessStrategy;
25+
private final RelationalParameters parameters;
26+
private final PartTree tree;
27+
private final R2dbcQueryCreator queryCreator;
28+
29+
/**
30+
* Creates new instance of this class with the given {@link R2dbcQueryMethod} and {@link DatabaseClient}.
31+
*
32+
* @param method query method (must not be {@literal null})
33+
* @param databaseClient database client (must not be {@literal null})
34+
* @param converter converter (must not be {@literal null})
35+
* @param dataAccessStrategy data access strategy (must not be {@literal null})
36+
*/
37+
public PartTreeR2dbcQuery(R2dbcQueryMethod method,
38+
DatabaseClient databaseClient,
39+
R2dbcConverter converter,
40+
ReactiveDataAccessStrategy dataAccessStrategy) {
41+
super(method, databaseClient, converter);
42+
Assert.notNull(dataAccessStrategy, "Data access strategy must not be null");
43+
this.dataAccessStrategy = dataAccessStrategy;
44+
this.parameters = method.getParameters();
45+
46+
RelationalEntityMetadata<?> entityMetadata = method.getEntityInformation();
47+
48+
try {
49+
this.tree = new PartTree(method.getName(), entityMetadata.getJavaType());
50+
validate(tree, parameters, method.getName());
51+
this.queryCreator = new R2dbcQueryCreator(tree);
52+
} catch (Exception e) {
53+
String message = String.format("Failed to create query for method %s! %s", method, e.getMessage());
54+
throw new IllegalArgumentException(message, e);
55+
}
56+
}
57+
58+
@Override
59+
protected BindableQuery createQuery(RelationalParameterAccessor accessor) {
60+
return queryCreator.createQuery(getDynamicSort(accessor));
61+
}
62+
63+
private Sort getDynamicSort(RelationalParameterAccessor accessor) {
64+
return parameters.potentiallySortsDynamically() ? accessor.getSort() : Sort.unsorted();
65+
}
66+
67+
private static void validate(PartTree tree, RelationalParameters parameters, String methodName) {
68+
int argCount = 0;
69+
Iterable<Part> parts = () -> tree.stream().flatMap(Streamable::stream).iterator();
70+
for (Part part : parts) {
71+
int numberOfArguments = part.getNumberOfArguments();
72+
for (int i = 0; i < numberOfArguments; i++) {
73+
throwExceptionOnArgumentMismatch(methodName, part, parameters, argCount);
74+
argCount++;
75+
}
76+
}
77+
}
78+
79+
private static void throwExceptionOnArgumentMismatch(String methodName,
80+
Part part,
81+
RelationalParameters parameters,
82+
int index) {
83+
Part.Type type = part.getType();
84+
String property = part.getProperty().toDotPath();
85+
86+
if (!parameters.getBindableParameters().hasParameterAt(index)) {
87+
String msgTemplate = "Method %s expects at least %d arguments but only found %d. " +
88+
"This leaves an operator of type %s for property %s unbound.";
89+
String formattedMsg = String.format(msgTemplate, methodName, index + 1, index, type.name(), property);
90+
throw new IllegalStateException(formattedMsg);
91+
}
92+
93+
RelationalParameters.RelationalParameter parameter = parameters.getBindableParameter(index);
94+
if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) {
95+
String message = wrongParameterTypeMessage(methodName, property, type, "Collection", parameter);
96+
throw new IllegalStateException(message);
97+
} else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) {
98+
String message = wrongParameterTypeMessage(methodName, property, type, "scalar", parameter);
99+
throw new IllegalStateException(message);
100+
}
101+
}
102+
103+
private static boolean expectsCollection(Part.Type type) {
104+
return type == Part.Type.IN || type == Part.Type.NOT_IN;
105+
}
106+
107+
private static boolean parameterIsCollectionLike(RelationalParameters.RelationalParameter parameter) {
108+
return Iterable.class.isAssignableFrom(parameter.getType()) || parameter.getType().isArray();
109+
}
110+
111+
private static boolean parameterIsScalarLike(RelationalParameters.RelationalParameter parameter) {
112+
return !Iterable.class.isAssignableFrom(parameter.getType());
113+
}
114+
115+
private static String wrongParameterTypeMessage(String methodName,
116+
String property,
117+
Part.Type operatorType,
118+
String expectedArgumentType,
119+
RelationalParameters.RelationalParameter parameter) {
120+
return String.format("Operator %s on %s requires a %s argument, found %s in method %s.", operatorType.name(),
121+
property, expectedArgumentType, parameter.getType(), methodName);
122+
}
123+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package org.springframework.data.r2dbc.repository.query;
2+
3+
import org.springframework.data.domain.Sort;
4+
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
5+
import org.springframework.data.repository.query.parser.Part;
6+
import org.springframework.data.repository.query.parser.PartTree;
7+
8+
import java.util.Iterator;
9+
import java.util.concurrent.locks.Condition;
10+
11+
/**
12+
* Implementation of {@link AbstractQueryCreator} that creates {@link BindableQuery} from a {@link PartTree}.
13+
*
14+
* @author Roman Chigvintsev
15+
*/
16+
class R2dbcQueryCreator extends AbstractQueryCreator<BindableQuery, Condition> {
17+
/**
18+
* Creates new instance of this class with the given {@link PartTree}.
19+
*
20+
* @param tree part tree (must not be {@literal null})
21+
*/
22+
public R2dbcQueryCreator(PartTree tree) {
23+
super(tree);
24+
}
25+
26+
@Override
27+
protected Condition create(Part part, Iterator<Object> iterator) {
28+
throw new UnsupportedOperationException();
29+
}
30+
31+
@Override
32+
protected Condition and(Part part, Condition condition, Iterator<Object> iterator) {
33+
throw new UnsupportedOperationException();
34+
}
35+
36+
@Override
37+
protected Condition or(Condition condition, Condition s1) {
38+
throw new UnsupportedOperationException();
39+
}
40+
41+
@Override
42+
protected BindableQuery complete(Condition condition, Sort sort) {
43+
throw new UnsupportedOperationException();
44+
}
45+
}

0 commit comments

Comments
 (0)