|
15 | 15 | */
|
16 | 16 | package org.springframework.data.jpa.repository.query;
|
17 | 17 |
|
| 18 | +import java.util.Collection; |
18 | 19 | import java.util.List;
|
19 | 20 | import java.util.Optional;
|
20 | 21 |
|
|
26 | 27 |
|
27 | 28 | import org.springframework.data.domain.Sort;
|
28 | 29 | import org.springframework.data.jpa.provider.PersistenceProvider;
|
| 30 | +import org.springframework.data.jpa.repository.query.JpaParameters.JpaParameter; |
29 | 31 | import org.springframework.data.jpa.repository.query.JpaQueryExecution.DeleteExecution;
|
30 | 32 | import org.springframework.data.jpa.repository.query.JpaQueryExecution.ExistsExecution;
|
31 | 33 | import org.springframework.data.jpa.repository.query.ParameterMetadataProvider.ParameterMetadata;
|
32 | 34 | import org.springframework.data.repository.query.ParametersParameterAccessor;
|
33 | 35 | import org.springframework.data.repository.query.ResultProcessor;
|
34 | 36 | import org.springframework.data.repository.query.ReturnedType;
|
| 37 | +import org.springframework.data.repository.query.parser.Part; |
| 38 | +import org.springframework.data.repository.query.parser.Part.Type; |
35 | 39 | import org.springframework.data.repository.query.parser.PartTree;
|
| 40 | +import org.springframework.data.repository.query.parser.PartTree.OrPart; |
36 | 41 | import org.springframework.lang.Nullable;
|
37 | 42 |
|
38 | 43 | /**
|
@@ -74,6 +79,7 @@ public class PartTreeJpaQuery extends AbstractJpaQuery {
|
74 | 79 | try {
|
75 | 80 |
|
76 | 81 | this.tree = new PartTree(method.getName(), domainClass);
|
| 82 | + validate(tree, parameters, method.toString()); |
77 | 83 | this.countQuery = new CountQueryPreparer(persistenceProvider, recreationRequired);
|
78 | 84 | this.query = tree.isCountProjection() ? countQuery : new QueryPreparer(persistenceProvider, recreationRequired);
|
79 | 85 |
|
@@ -118,6 +124,74 @@ protected JpaQueryExecution getExecution() {
|
118 | 124 | return super.getExecution();
|
119 | 125 | }
|
120 | 126 |
|
| 127 | + private static void validate(PartTree tree, JpaParameters parameters, String methodName) { |
| 128 | + |
| 129 | + int argCount = 0; |
| 130 | + |
| 131 | + for (OrPart orPart : tree) { |
| 132 | + |
| 133 | + for (Part part : orPart) { |
| 134 | + |
| 135 | + int numberOfArguments = part.getNumberOfArguments(); |
| 136 | + |
| 137 | + for (int i = 0; i < numberOfArguments; i++) { |
| 138 | + |
| 139 | + throwExceptionOnArgumentMismatch(methodName, part, parameters, argCount); |
| 140 | + |
| 141 | + argCount++; |
| 142 | + } |
| 143 | + } |
| 144 | + } |
| 145 | + } |
| 146 | + |
| 147 | + private static void throwExceptionOnArgumentMismatch(String methodName, Part part, JpaParameters parameters, |
| 148 | + int index) { |
| 149 | + |
| 150 | + Type type = part.getType(); |
| 151 | + String property = part.getProperty().toDotPath(); |
| 152 | + |
| 153 | + if (!parameters.getBindableParameters().hasParameterAt(index)) { |
| 154 | + throw new IllegalStateException(String.format( |
| 155 | + "For the method %s we expect at least %d arguments but only found %d. This leaves an operator of type %s for property %s unbound.", |
| 156 | + methodName, index + 1, index, type.name(), property)); |
| 157 | + } |
| 158 | + |
| 159 | + JpaParameter parameter = parameters.getBindableParameter(index); |
| 160 | + |
| 161 | + if (expectsCollection(type) && !parameterIsCollectionLike(parameter)) { |
| 162 | + throw new IllegalStateException(wrongParameterTypeMessage(methodName, property, type, "Collection", parameter)); |
| 163 | + } else if (!expectsCollection(type) && !parameterIsScalarLike(parameter)) { |
| 164 | + throw new IllegalStateException(wrongParameterTypeMessage(methodName, property, type, "scalar", parameter)); |
| 165 | + } |
| 166 | + } |
| 167 | + |
| 168 | + private static String wrongParameterTypeMessage(String methodName, String property, Type operatorType, |
| 169 | + String expectedArgumenType, JpaParameter parameter) { |
| 170 | + |
| 171 | + return String.format( // |
| 172 | + "The operator %s on %s requires a %s argument, but we found %s in method %s", // |
| 173 | + operatorType.name(), // |
| 174 | + property, expectedArgumenType, // |
| 175 | + parameter.getType(), // |
| 176 | + methodName // |
| 177 | + ); |
| 178 | + } |
| 179 | + |
| 180 | + private static boolean parameterIsCollectionLike(JpaParameter parameter) { |
| 181 | + return Collection.class.isAssignableFrom(parameter.getType()) || parameter.getType().isArray(); |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Arrays are may be treated as collection like or in the case of binary data as scalar |
| 186 | + */ |
| 187 | + private static boolean parameterIsScalarLike(JpaParameter parameter) { |
| 188 | + return !Collection.class.isAssignableFrom(parameter.getType()); |
| 189 | + } |
| 190 | + |
| 191 | + private static boolean expectsCollection(Type type) { |
| 192 | + return type == Type.IN || type == Type.NOT_IN; |
| 193 | + } |
| 194 | + |
121 | 195 | /**
|
122 | 196 | * Query preparer to create {@link CriteriaQuery} instances and potentially cache them.
|
123 | 197 | *
|
|
0 commit comments