From 66653b99183fef4761330c223ec953ef723af517 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Fri, 20 Jun 2025 14:04:09 +0200 Subject: [PATCH] Add Collation to generated query if present. See #5004 Original pull request: #5005 --- .../MongoAotRepositoryFragmentSupport.java | 35 +++++++++++++++ .../mongodb/repository/aot/QueryBlocks.java | 19 +++++++- .../aot/QueryMethodContributionUnitTests.java | 44 +++++++++++++++++-- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoAotRepositoryFragmentSupport.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoAotRepositoryFragmentSupport.java index 6686f9794..84de3bb83 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoAotRepositoryFragmentSupport.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/MongoAotRepositoryFragmentSupport.java @@ -17,6 +17,7 @@ package org.springframework.data.mongodb.repository.aot; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import java.util.Map; import org.bson.Document; @@ -31,6 +32,7 @@ import org.springframework.data.mongodb.core.aggregation.AggregationPipeline; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.mapping.FieldName; import org.springframework.data.mongodb.core.query.BasicQuery; +import org.springframework.data.mongodb.core.query.Collation; import org.springframework.data.mongodb.repository.query.MongoParameters; import org.springframework.data.mongodb.util.json.ParameterBindingContext; import org.springframework.data.mongodb.util.json.ParameterBindingDocumentCodec; @@ -106,6 +108,39 @@ public class MongoAotRepositoryFragmentSupport { return new ParameterBindingDocumentCodec().decode(source, bindingContext); } + protected Object evaluate(String source, Map parameters) { + + ValueEvaluationContext valueEvaluationContext = this.valueExpressionDelegate.getEvaluationContextAccessor() + .create(new NoMongoParameters()).getEvaluationContext(parameters.values()); + + EvaluationContext evaluationContext = valueEvaluationContext.getEvaluationContext(); + parameters.forEach(evaluationContext::setVariable); + + ValueExpression parse = valueExpressionDelegate.getValueExpressionParser().parse(source); + return parse.evaluate(valueEvaluationContext); + } + + protected Collation collationOf(@Nullable Object source) { + + if(source == null) { + return Collation.simple(); + } + if (source instanceof String) { + return Collation.parse(source.toString()); + } + if (source instanceof Locale locale) { + return Collation.of(locale); + } + if (source instanceof Document document) { + return Collation.from(document); + } + if (source instanceof Collation collation) { + return collation; + } + throw new IllegalArgumentException( + "Unsupported collation source [%s]".formatted(ObjectUtils.nullSafeClassName(source))); + } + protected BasicQuery createQuery(String queryString, Object[] parameters) { Document queryDocument = bindParameters(queryString, parameters); diff --git a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java index e9425dce8..e01462547 100644 --- a/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java +++ b/spring-data-mongodb/src/main/java/org/springframework/data/mongodb/repository/aot/QueryBlocks.java @@ -29,6 +29,7 @@ import org.springframework.data.geo.Circle; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.ExecutableFindOperation.FindWithQuery; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.annotation.Collation; import org.springframework.data.mongodb.core.geo.GeoJson; import org.springframework.data.mongodb.core.geo.Sphere; import org.springframework.data.mongodb.core.query.BasicQuery; @@ -264,12 +265,26 @@ class QueryBlocks { } String comment = metaAnnotation.getString("comment"); - if (StringUtils.hasText("comment")) { + if (StringUtils.hasText(comment)) { builder.addStatement("$L.comment($S)", queryVariableName, comment); } } - // TODO: Meta annotation: Disk usage + MergedAnnotation collationAnnotation = context.getAnnotation(Collation.class); + if (collationAnnotation.isPresent()) { + + String collationString = collationAnnotation.getString("value"); + if(StringUtils.hasText(collationString)) { + if (!MongoCodeBlocks.containsPlaceholder(collationString)) { + builder.addStatement("$L.collation($T.parse($S))", queryVariableName, + org.springframework.data.mongodb.core.query.Collation.class, collationString); + } else { + builder.add("$L.collation(collationOf(evaluate($S, ", queryVariableName, collationString); + builder.add(MongoCodeBlocks.renderArgumentMap(arguments)); + builder.add(")));\n"); + } + } + } return builder.build(); } diff --git a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/QueryMethodContributionUnitTests.java b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/QueryMethodContributionUnitTests.java index ef62b7969..d3d0f40e4 100644 --- a/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/QueryMethodContributionUnitTests.java +++ b/spring-data-mongodb/src/test/java/org/springframework/data/mongodb/repository/aot/QueryMethodContributionUnitTests.java @@ -22,6 +22,7 @@ import example.aot.UserRepository; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.List; import java.util.regex.Pattern; import javax.lang.model.element.Modifier; @@ -37,8 +38,10 @@ import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Point; import org.springframework.data.geo.Polygon; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.annotation.Collation; import org.springframework.data.mongodb.core.geo.GeoJsonPolygon; import org.springframework.data.mongodb.core.geo.Sphere; +import org.springframework.data.mongodb.repository.Hint; import org.springframework.data.mongodb.repository.ReadPreference; import org.springframework.data.repository.Repository; import org.springframework.data.repository.aot.generate.AotQueryMethodGenerationContext; @@ -211,8 +214,36 @@ public class QueryMethodContributionUnitTests { MethodSpec methodSpec = codeOf(UserRepository.class, "findByFirstnameRegex", Pattern.class); assertThat(methodSpec.toString()) // - .contains("createQuery(\"{'firstname':{'$regex':?0}}\"") // - .contains("Object[]{ pattern }"); + .contains("createQuery(\"{'firstname':{'$regex':?0}}\"") // + .contains("Object[]{ pattern }"); + } + + @Test // GH-4939 + void rendersHint() throws NoSuchMethodException { + + MethodSpec methodSpec = codeOf(UserRepoWithMeta.class, "findByFirstname", String.class); + + assertThat(methodSpec.toString()) // + .contains(".withHint(\"fn-idx\")"); + } + + @Test // GH-4939 + void rendersCollation() throws NoSuchMethodException { + + MethodSpec methodSpec = codeOf(UserRepoWithMeta.class, "findByFirstname", String.class); + + assertThat(methodSpec.toString()) // + .containsPattern(".*\\.collation\\(.*Collation\\.parse\\(\"en_US\"\\)\\)"); + } + + @Test // GH-4939 + void rendersCollationFromExpression() throws NoSuchMethodException { + + MethodSpec methodSpec = codeOf(UserRepoWithMeta.class, "findWithCollationByFirstname", String.class, String.class); + + assertThat(methodSpec.toString()) // + .containsIgnoringWhitespaces( + "collationOf(evaluate(\"?#{[1]}\", java.util.Map.of(\"firstname\", firstname, \"locale\", locale)))"); } private static MethodSpec codeOf(Class repository, String methodName, Class... args) @@ -228,7 +259,7 @@ public class QueryMethodContributionUnitTests { Assertions.fail("No contribution for method %s.%s(%s)".formatted(repository.getSimpleName(), methodName, Arrays.stream(args).map(Class::getSimpleName).toList())); } - AotRepositoryFragmentMetadata metadata = new AotRepositoryFragmentMetadata(ClassName.get(UserRepository.class)); + AotRepositoryFragmentMetadata metadata = new AotRepositoryFragmentMetadata(ClassName.get(repository)); metadata.addField( FieldSpec.builder(MongoOperations.class, "mongoOperations", Modifier.PRIVATE, Modifier.FINAL).build()); @@ -247,6 +278,13 @@ public class QueryMethodContributionUnitTests { interface UserRepoWithMeta extends Repository { + @Hint("fn-idx") + @Collation("en_US") + List findByFirstname(String firstname); + + @Collation("?#{[1]}") + List findWithCollationByFirstname(String firstname, String locale); + @ReadPreference("NEAREST") GeoResults findByLocationCoordinatesNear(Point point, Distance maxDistance); }