Skip to content

Commit a2d2e3e

Browse files
committed
Between, Like.
1 parent 42fe0d8 commit a2d2e3e

File tree

9 files changed

+136
-63
lines changed

9 files changed

+136
-63
lines changed

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/AotRepositoryFragmentSupport.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,15 @@ protected SqlParameterSource escapingParameterSource(SqlParameterSource paramete
144144
return new EscapingParameterSource(parameterSource, getDialect().getLikeEscaper());
145145
}
146146

147+
protected @Nullable Object escape(@Nullable Object value) {
148+
149+
if (value == null) {
150+
return value;
151+
}
152+
153+
return getDialect().getLikeEscaper().escape(value.toString());
154+
}
155+
147156
protected BindValue getBindableValue(Method method, @Nullable Object value, String parameterReference) {
148157
return getBindableValue(parameters.get().get(method).getParameter(parameterReference), value);
149158
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/CapturingParameterMetadataProvider.java

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@
1515
*/
1616
package org.springframework.data.jdbc.repository.aot;
1717

18+
import java.sql.JDBCType;
19+
1820
import org.jspecify.annotations.Nullable;
1921

2022
import org.springframework.data.jdbc.core.mapping.JdbcValue;
2123
import org.springframework.data.jdbc.repository.query.ParameterBinding;
2224
import org.springframework.data.relational.repository.query.ParameterMetadataProvider;
2325
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
2426
import org.springframework.data.repository.query.parser.Part;
27+
import org.springframework.util.Assert;
2528

2629
/**
2730
* Extension to {@link ParameterMetadataProvider} that captures the {@link ParameterBinding} for each parameter along
@@ -39,11 +42,15 @@ public CapturingParameterMetadataProvider(RelationalParameterAccessor accessor)
3942
@Override
4043
protected Object prepareParameterValue(@Nullable Object value, Class<?> valueType, Part.Type partType) {
4144

42-
Object prepared = super.prepareParameterValue(value, valueType, partType);
45+
CapturingJdbcValue capturingJdbcValue = (CapturingJdbcValue) value;
46+
47+
if (partType == Part.Type.STARTING_WITH || partType == Part.Type.ENDING_WITH || partType == Part.Type.CONTAINING
48+
|| partType == Part.Type.NOT_CONTAINING) {
49+
return capturingJdbcValue.withBinding(ParameterBinding.like(capturingJdbcValue.getBinding(), partType));
50+
}
4351

44-
return JdbcValue.of(value instanceof JdbcValue jv && jv.getValue() instanceof ParameterBinding pb
45-
? new CapturingJdbcValue(prepared, pb)
46-
: prepared, null);
52+
return JdbcValue.of(capturingJdbcValue.withValue(super.prepareParameterValue(value, valueType, partType)),
53+
JDBCType.OTHER);
4754
}
4855

4956
static class CapturingJdbcValue extends JdbcValue {
@@ -52,6 +59,7 @@ static class CapturingJdbcValue extends JdbcValue {
5259

5360
protected CapturingJdbcValue(@Nullable Object value, ParameterBinding binding) {
5461
super(value, null);
62+
Assert.notNull(binding, "Parameter binding must not be null");
5563
this.binding = binding;
5664
}
5765

@@ -76,5 +84,18 @@ public ParameterBinding getBinding() {
7684
public String toString() {
7785
return "s";
7886
}
87+
88+
public CapturingJdbcValue withValue(@Nullable Object value) {
89+
90+
if (value == this) {
91+
return this;
92+
}
93+
94+
return new CapturingJdbcValue(value, binding);
95+
}
96+
97+
public CapturingJdbcValue withBinding(ParameterBinding binding) {
98+
return new CapturingJdbcValue(getValue(), binding);
99+
}
79100
}
80101
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/JdbcCodeBlocks.java

Lines changed: 36 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.data.relational.core.sql.LockMode;
4747
import org.springframework.data.relational.repository.Lock;
4848
import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext;
49+
import org.springframework.data.repository.query.parser.Part;
4950
import org.springframework.data.support.PageableExecutionUtils;
5051
import org.springframework.data.util.Pair;
5152
import org.springframework.javapoet.CodeBlock;
@@ -90,6 +91,7 @@ static QueryExecutionBlockBuilder executionBuilder(AotQueryMethodGenerationConte
9091
static class QueryBlockBuilder {
9192

9293
private final AotQueryMethodGenerationContext context;
94+
private final JdbcQueryMethod queryMethod;
9395
private final String parameterNames;
9496
private String queryVariableName = "undefined";
9597
private String parameterSourceVariableName = "undefined";
@@ -99,6 +101,7 @@ static class QueryBlockBuilder {
99101
private QueryBlockBuilder(AotQueryMethodGenerationContext context, JdbcQueryMethod queryMethod) {
100102

101103
this.context = context;
104+
this.queryMethod = queryMethod;
102105

103106
String parameterNames = StringUtils.collectionToDelimitedString(context.getAllParameterNames(), ", ");
104107

@@ -145,7 +148,11 @@ public CodeBlock build() {
145148
queries.count() instanceof DerivedAotQuery derivedCountQuery ? derivedCountQuery : null);
146149
}
147150

148-
return createStringQuery(queryVariableName, parameterSourceVariableName, queries.result());
151+
if (queries.result() instanceof StringAotQuery stringQuery) {
152+
return createStringQuery(queryVariableName, parameterSourceVariableName, stringQuery);
153+
}
154+
155+
throw new IllegalArgumentException("Unsupported AOT query type: " + queries.result());
149156
}
150157

151158
private CodeBlock createDerivedQuery(DerivedAotQuery entityQuery, @Nullable DerivedAotQuery countQuery) {
@@ -205,6 +212,8 @@ private CodeBlock buildQuery(boolean count, DerivedAotQuery aotQuery, CriteriaDe
205212
method = "count($T.class)";
206213
} else if (aotQuery.isExists()) {
207214
method = "exists($T.class)";
215+
} else if (queryMethod.isSliceQuery()) {
216+
method = "slice($T.class)";
208217
} else {
209218
method = "select($T.class)";
210219
}
@@ -221,7 +230,7 @@ private CodeBlock buildQuery(boolean count, DerivedAotQuery aotQuery, CriteriaDe
221230
}
222231

223232
if (StringUtils.hasText(context.getPageableParameterName())) {
224-
builder.addStatement("$L.with($L)", selection, context.getPageableParameterName());
233+
builder.addStatement("$L.page($L)", selection, context.getPageableParameterName());
225234
}
226235

227236
Sort sort = aotQuery.getSort();
@@ -242,7 +251,7 @@ private CodeBlock buildQuery(boolean count, DerivedAotQuery aotQuery, CriteriaDe
242251
builder.addStatement("$L.filter($L)", selection, context.localVariable("criteria"));
243252
}
244253

245-
// TODO Projections, Pagination
254+
// TODO Projections
246255

247256
builder.addStatement("$1T $2L = new $1T()", MapSqlParameterSource.class, rawParameterSource);
248257
builder.addStatement("$T $L = $L.build($L)", String.class, queryVariableName, selection, rawParameterSource);
@@ -353,8 +362,8 @@ private void appendCriteria(CriteriaDefinition current, Builder builder) {
353362
case GTE -> builder.add(".greaterThanEquals($L)", renderPlaceholder(value));
354363
case IS_NULL -> builder.add(".isNull()");
355364
case IS_NOT_NULL -> builder.add(".isNotNull()");
356-
case LIKE -> builder.add(".like($L)", renderPlaceholder(value));
357-
case NOT_LIKE -> builder.add(".notLike($L)", renderPlaceholder(value));
365+
case LIKE -> applyLike(builder, "like", value);
366+
case NOT_LIKE -> applyLike(builder, "notLike", value);
358367
case NOT_IN -> builder.add(".notIn($L)", renderPlaceholder(value));
359368
case IN -> builder.add(".in($L)", renderPlaceholder(value));
360369
case IS_TRUE -> builder.add(".isTrue()");
@@ -366,6 +375,25 @@ private void appendCriteria(CriteriaDefinition current, Builder builder) {
366375
}
367376
}
368377

378+
private void applyLike(Builder builder, String method, @Nullable Object value) {
379+
380+
CapturingJdbcValue captured = CapturingJdbcValue.unwrap(value);
381+
382+
String likeValue = "$L";
383+
if (captured.getBinding() instanceof ParameterBinding.LikeParameterBinding lpb) {
384+
385+
if (lpb.getType() == Part.Type.CONTAINING) {
386+
likeValue = "\"%\" + escape($L) + \"%\"";
387+
} else if (lpb.getType() == Part.Type.STARTING_WITH) {
388+
likeValue = "escape($L) + \"%\"";
389+
} else if (lpb.getType() == Part.Type.ENDING_WITH) {
390+
likeValue = "\"%\" + escape($L)";
391+
}
392+
}
393+
394+
builder.add(".$L(" + likeValue + ")", method, renderPlaceholder(value));
395+
}
396+
369397
private @Nullable String renderPlaceholder(@Nullable Object value) {
370398

371399
CapturingJdbcValue captured = CapturingJdbcValue.unwrap(value);
@@ -379,14 +407,14 @@ private void appendCriteria(CriteriaDefinition current, Builder builder) {
379407
}
380408

381409
private Object renderPlaceholder(@Nullable Object value, int index) {
382-
return index == 0 ? ((Pair) value).getFirst() : ((Pair) value).getSecond();
410+
return renderPlaceholder(index == 0 ? ((Pair) value).getFirst() : ((Pair) value).getSecond());
383411
}
384412

385-
private CodeBlock createStringQuery(String queryVariableName, String parameterSourceName, AotQuery query) {
413+
private CodeBlock createStringQuery(String queryVariableName, String parameterSourceName, StringAotQuery query) {
386414

387415
Builder builder = CodeBlock.builder();
388416

389-
builder.add(doCreateQuery(queryVariableName, query));
417+
builder.addStatement("$T $L = $S", String.class, queryVariableName, query.getQueryString());
390418
builder.addStatement("$1T $2L = new $1T()", MapSqlParameterSource.class, parameterSourceName);
391419

392420
for (ParameterBinding binding : query.getParameterBindings()) {
@@ -398,24 +426,6 @@ private CodeBlock createStringQuery(String queryVariableName, String parameterSo
398426
return builder.build();
399427
}
400428

401-
private static boolean isArray(Class<?> parameterType) {
402-
return parameterType.isArray() && !parameterType.getComponentType().equals(byte.class)
403-
&& !parameterType.getComponentType().equals(Byte.class);
404-
}
405-
406-
private CodeBlock doCreateQuery(String queryVariableName, AotQuery query) {
407-
408-
Builder builder = CodeBlock.builder();
409-
410-
if (query instanceof StringAotQuery sq) {
411-
412-
builder.addStatement("$T $L = $S", String.class, queryVariableName, sq.getQueryString());
413-
return builder.build();
414-
}
415-
416-
throw new UnsupportedOperationException("Unsupported query type: " + query);
417-
}
418-
419429
private String getParameterName(ParameterBinding.BindingIdentifier identifier) {
420430
return identifier.hasName() ? identifier.getName() : Integer.toString(identifier.getPosition());
421431
}

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/aot/QueriesFactory.java

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.io.IOException;
1919
import java.lang.reflect.Method;
20-
import java.sql.JDBCType;
2120
import java.util.ArrayList;
2221
import java.util.List;
2322
import java.util.Objects;
@@ -31,7 +30,6 @@
3130
import org.springframework.data.domain.Sort;
3231
import org.springframework.data.jdbc.core.convert.JdbcConverter;
3332
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
34-
import org.springframework.data.jdbc.core.mapping.JdbcValue;
3533
import org.springframework.data.jdbc.repository.aot.CapturingParameterMetadataProvider.CapturingJdbcValue;
3634
import org.springframework.data.jdbc.repository.config.JdbcRepositoryConfigExtension;
3735
import org.springframework.data.jdbc.repository.query.JdbcParameters;
@@ -41,7 +39,6 @@
4139
import org.springframework.data.jdbc.repository.query.ParametrizedQuery;
4240
import org.springframework.data.jdbc.repository.query.Query;
4341
import org.springframework.data.relational.core.dialect.Dialect;
44-
import org.springframework.data.relational.core.query.ValueFunction;
4542
import org.springframework.data.relational.repository.query.ParameterMetadataProvider;
4643
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
4744
import org.springframework.data.relational.repository.query.RelationalParameters;
@@ -189,23 +186,6 @@ protected ParameterMetadataProvider getParameterMetadataProvider(RelationalParam
189186

190187
ParametrizedQuery query = queryCreator.createQuery(Sort.unsorted());
191188

192-
for (String parameterName : query.getParameterSource().getParameterNames()) {
193-
194-
CapturingJdbcValue captured = CapturingJdbcValue.unwrap(query.getParameterSource().getValue(parameterName));
195-
196-
if (captured.getValue() instanceof ValueFunction<?> vf) {
197-
198-
Object escaped = vf.apply(dialect.getLikeEscaper());
199-
200-
if (escaped != null && (escaped.equals("%s") || escaped.equals("s%") || escaped.equals("%s%"))) {
201-
bindings.add(ParameterBinding.like(parameterName, captured.getBinding().getOrigin(), escaped.toString()));
202-
continue;
203-
}
204-
}
205-
206-
bindings.add(ParameterBinding.named(parameterName, captured.getBinding().getOrigin()));
207-
}
208-
209189
return new DerivedAotQuery(query.getQuery(), bindings, query.getCriteria(), partTree.getSort(),
210190
partTree.getResultLimit(), partTree.isDelete(), partTree.isCountProjection(), partTree.isExistsProjection());
211191
}
@@ -237,16 +217,16 @@ public Object[] getValues() {
237217
protected <T> @Nullable T getValue(int index) {
238218

239219
RelationalParameters.RelationalParameter parameter = parameters.getParameter(index);
240-
return (T) JdbcValue.of(ParameterBinding.named(parameter.getRequiredName(),
241-
ParameterBinding.ParameterOrigin.ofParameter(parameter.getIndex())), JDBCType.OTHER);
220+
return (T) new CapturingJdbcValue(null, ParameterBinding.named(parameter.getRequiredName(),
221+
ParameterBinding.ParameterOrigin.ofParameter(parameter.getIndex())));
242222
}
243223

244224
@Override
245225
public @Nullable Object getBindableValue(int index) {
246226

247227
RelationalParameters.RelationalParameter parameter = bindable.getParameter(index);
248-
return JdbcValue.of(ParameterBinding.named(parameter.getRequiredName(),
249-
ParameterBinding.ParameterOrigin.ofParameter(parameter.getIndex())), JDBCType.OTHER);
228+
return new CapturingJdbcValue(null, ParameterBinding.named(parameter.getRequiredName(),
229+
ParameterBinding.ParameterOrigin.ofParameter(parameter.getIndex())));
250230
}
251231
};
252232
return accessor;

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) {
202202

203203
StatementFactory.Selection selection = getSelection(entity);
204204

205-
selection.with(accessor.getPageable()).filter(criteria).orderBy(sort);
205+
selection.page(accessor.getPageable()).filter(criteria).orderBy(sort);
206206

207207
if (this.lockMode.isPresent()) {
208208
selection.lock(this.lockMode.get().value());

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/ParameterBinding.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,16 @@ public static ParameterBinding named(String name, ParameterOrigin origin) {
7878
}
7979

8080
/**
81-
* Creates a new {@link ParameterBinding} for the named parameter with the given name and origin.
81+
* Creates a new {@code LIKE} {@link ParameterBinding} for the given {@link ParameterBinding} applying the part
82+
* {@code Type}.
8283
*
83-
* @param name
84-
* @param origin
84+
* @param binding
85+
* @param partType
8586
* @return
8687
*/
87-
public static ParameterBinding like(String name, ParameterOrigin origin, String probe) {
88-
return new LikeParameterBinding(BindingIdentifier.of(name), origin, LikeParameterBinding.getLikeTypeFrom(probe));
88+
89+
public static ParameterBinding like(ParameterBinding binding, Type partType) {
90+
return new LikeParameterBinding(binding.getIdentifier(), binding.getOrigin(), partType);
8991
}
9092

9193
public BindingIdentifier getIdentifier() {
@@ -183,7 +185,7 @@ public String toString() {
183185
* Represents a parameter binding in a JDBC query augmented with instructions of how to apply a parameter as LIKE
184186
* parameter.
185187
*/
186-
static class LikeParameterBinding extends ParameterBinding {
188+
public static class LikeParameterBinding extends ParameterBinding {
187189

188190
private static final List<Type> SUPPORTED_TYPES = Arrays.asList(Type.CONTAINING, Type.STARTING_WITH,
189191
Type.ENDING_WITH, Type.LIKE);

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/StatementFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public Selection orderBy(Sort sort) {
128128
return this;
129129
}
130130

131-
public Selection with(@org.jspecify.annotations.Nullable Pageable pageable) {
131+
public Selection page(@org.jspecify.annotations.Nullable Pageable pageable) {
132132

133133
if (pageable != null) {
134134
this.pageable = pageable;

0 commit comments

Comments
 (0)