diff --git a/core/src/main/java/dev/morphia/aggregation/expressions/ArrayExpressions.java b/core/src/main/java/dev/morphia/aggregation/expressions/ArrayExpressions.java index 57148ebb3c6..00b88386c6e 100644 --- a/core/src/main/java/dev/morphia/aggregation/expressions/ArrayExpressions.java +++ b/core/src/main/java/dev/morphia/aggregation/expressions/ArrayExpressions.java @@ -5,6 +5,7 @@ import dev.morphia.aggregation.expressions.impls.ArrayIndexExpression; import dev.morphia.aggregation.expressions.impls.ArrayLiteral; import dev.morphia.aggregation.expressions.impls.Expression; +import dev.morphia.aggregation.expressions.impls.FilterExpression; import dev.morphia.aggregation.expressions.impls.MapExpression; import dev.morphia.aggregation.expressions.impls.RangeExpression; import dev.morphia.aggregation.expressions.impls.ReduceExpression; @@ -76,6 +77,18 @@ public static Expression elementAt(Object array, Object index) { return new Expression("$arrayElemAt", wrap(List.of(array, index))); } + /** + * Selects a subset of the array to return an array with only the elements that match the filter condition. + * + * @param array the array to use + * @param conditional the conditional to use for filtering + * @return the new expression + * @aggregation.expression $filter + */ + public static FilterExpression filter(Expression array, Expression conditional) { + return Expressions.filter(array, conditional); + } + /** * Returns a boolean indicating whether a specified value is in an array. * diff --git a/core/src/main/java/dev/morphia/aggregation/expressions/Expressions.java b/core/src/main/java/dev/morphia/aggregation/expressions/Expressions.java index 707ffb30b90..13f3ee18aff 100644 --- a/core/src/main/java/dev/morphia/aggregation/expressions/Expressions.java +++ b/core/src/main/java/dev/morphia/aggregation/expressions/Expressions.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.stream.Collectors; -import com.mongodb.lang.Nullable; - import dev.morphia.aggregation.expressions.impls.DocumentExpression; import dev.morphia.aggregation.expressions.impls.Expression; import dev.morphia.aggregation.expressions.impls.FilterExpression; @@ -53,18 +51,6 @@ public static DocumentExpression document(String name, Object expression) { .field(name, expression); } - /** - * Creates a field expression for the given value. If the value does not already start with '$', it will be prepended automatically. - * - * @param name the field name - * @return the new expression - * @deprecated - */ - @Deprecated(since = "3.0", forRemoval = true) - public static Expression field(String name) { - return new ValueExpression(name.startsWith("$") ? name : "$" + name); - } - /** * @param input An expression that resolves to an array. * @param cond An expression that resolves to a boolean value used to determine if an element should be included in the output array. @@ -139,19 +125,6 @@ public static List toList(T... elements) { return new ArrayList<>(asList(elements)); } - /** - * Returns a value without parsing. Note that this is different from {@link #literal(Object)} in that the given value will dropped - * directly in to the pipeline for use/evaluation in whatever context the value is used. - * - * @param value the value - * @return the new expression - * @deprecated - */ - @Deprecated(since = "3.0", forRemoval = true) - public static ValueExpression value(@Nullable Object value) { - return new ValueExpression(value); - } - /** * @hidden * @param value diff --git a/rewrite/src/main/java/dev/morphia/rewrite/recipes/PipelineRewrite.java b/rewrite/src/main/java/dev/morphia/rewrite/recipes/PipelineRewrite.java index 1e4c013b459..3c8e172aa62 100644 --- a/rewrite/src/main/java/dev/morphia/rewrite/recipes/PipelineRewrite.java +++ b/rewrite/src/main/java/dev/morphia/rewrite/recipes/PipelineRewrite.java @@ -21,6 +21,7 @@ import org.openrewrite.java.tree.MethodCall; import org.openrewrite.java.tree.Space; +import static dev.morphia.rewrite.recipes.RegexPatternMerge.findInvocation; import static java.util.Collections.emptyList; public class PipelineRewrite extends Recipe { @@ -93,35 +94,47 @@ public boolean matches(@Nullable MethodCall methodCall) { @Override public @NotNull MethodInvocation visitMethodInvocation(@NotNull MethodInvocation methodInvocation, @NotNull ExecutionContext context) { + MethodInvocation pipeline = findInvocation(methodInvocation, "pipeline"); if (MEGA_MATCHER.matches(methodInvocation)) { - var updated = methodInvocation; - while (MEGA_MATCHER.matches(updated) && MEGA_MATCHER.matches(updated.getSelect())) { - final List args = new ArrayList<>(); - MethodInvocation invocation = (MethodInvocation) updated.getSelect(); - if (invocation.getSimpleName().equals("match")) { - maybeAddImport(Match.class.getName(), "match", false); - MethodInvocation applied = MATCH.apply(new Cursor(getCursor(), invocation), - invocation.getCoordinates().replaceMethod()); - args.add(applied.withArguments(invocation.getArguments()).withSelect(null)); - } else { - args.addAll(invocation.getArguments()); - } - args.addAll(updated.getArguments()); - Space after = methodInvocation.getPadding().getSelect().getAfter(); - Space space = Space.build(getIndent(after), emptyList()); - var list = args.stream() - .map(a -> (Expression) a.withPrefix(space)) - .toList(); - - updated = invocation.withArguments(list); + Expression updated = methodInvocation; + MethodInvocation invocation = (MethodInvocation) updated; + List arguments = new ArrayList<>(); + while (MEGA_MATCHER.matches(updated)/* && MEGA_MATCHER.matches(updated.getSelect()) */) { + invocation = (MethodInvocation) updated; + collectArguments(arguments, invocation); + + updated = invocation.getSelect(); + } - return maybeAutoFormat(methodInvocation, updated.withName(methodInvocation.getName().withSimpleName("pipeline")) + Space after = methodInvocation.getPadding().getSelect().getAfter(); + Space space = Space.build(getIndent(after), emptyList()); + arguments = arguments.stream() + .map(a -> (Expression) a.withPrefix(space)) + .toList(); + + invocation = invocation.withName(methodInvocation.getName().withSimpleName("pipeline")) + .withArguments(arguments); + + after = invocation.getPadding().getSelect().getAfter(); + + return maybeAutoFormat(methodInvocation, invocation .withPrefix(Space.build("\n", emptyList())), context); } else { return super.visitMethodInvocation(methodInvocation, context); } } + private void collectArguments(List args, MethodInvocation invocation) { + if (invocation.getSimpleName().equals("match")) { + maybeAddImport(Match.class.getName(), "match", false); + MethodInvocation applied = MATCH.apply(new Cursor(getCursor(), invocation), + invocation.getCoordinates().replaceMethod()); + args.add(0, applied.withArguments(invocation.getArguments()).withSelect(null)); + } else { + args.addAll(0, invocation.getArguments()); + } + } + }; } diff --git a/rewrite/src/main/java/dev/morphia/rewrite/recipes/RegexPatternMerge.java b/rewrite/src/main/java/dev/morphia/rewrite/recipes/RegexPatternMerge.java index b13334f37b5..53394e76bb3 100644 --- a/rewrite/src/main/java/dev/morphia/rewrite/recipes/RegexPatternMerge.java +++ b/rewrite/src/main/java/dev/morphia/rewrite/recipes/RegexPatternMerge.java @@ -38,7 +38,7 @@ public TreeVisitor getVisitor() { public MethodInvocation visitMethodInvocation(@NotNull MethodInvocation invocation, @NotNull ExecutionContext executionContext) { if (MATCHER.matches(invocation)) { - MethodInvocation pattern = findPattern(invocation); + MethodInvocation pattern = findInvocation(invocation, "pattern"); if (pattern != null) { var arguments = pattern.getArguments(); Expression select = invocation; @@ -70,15 +70,15 @@ public MethodInvocation visitMethodInvocation(@NotNull MethodInvocation invocati } @Nullable - private MethodInvocation findPattern(Expression expression) { + public static MethodInvocation findInvocation(Expression expression, String methodName) { if (expression == null) { return null; } if (expression instanceof MethodInvocation invocation) { - if (invocation.getSimpleName().equals("pattern")) { + if (invocation.getSimpleName().equals(methodName)) { return invocation; } else { - return findPattern(invocation.getSelect()); + return findInvocation(invocation.getSelect(), methodName); } } return null; diff --git a/rewrite/src/main/java/dev/morphia/rewrite/recipes/UnwrapFieldExpressions.java b/rewrite/src/main/java/dev/morphia/rewrite/recipes/UnwrapFieldExpressions.java index 0a5652e794a..998681d2892 100644 --- a/rewrite/src/main/java/dev/morphia/rewrite/recipes/UnwrapFieldExpressions.java +++ b/rewrite/src/main/java/dev/morphia/rewrite/recipes/UnwrapFieldExpressions.java @@ -20,8 +20,8 @@ public String getDescription() { @Override public JavaVisitor getVisitor() { - MethodMatcher fieldMatcher = new MethodMatcher("dev.morphia.aggregation.expressions.Expressions field(String)"); - MethodMatcher valueMatcher = new MethodMatcher("dev.morphia.aggregation.expressions.Expressions value(Object)"); + MethodMatcher fieldMatcher = new MethodMatcher("dev.morphia.aggregation.expressions.Expressions field(..)"); + MethodMatcher valueMatcher = new MethodMatcher("dev.morphia.aggregation.expressions.Expressions value(..)"); return new JavaVisitor<>() { diff --git a/rewrite/src/main/resources/META-INF/rewrite/rewrite.yml b/rewrite/src/main/resources/META-INF/rewrite/rewrite.yml index 3b95bc59e4a..6153979162b 100644 --- a/rewrite/src/main/resources/META-INF/rewrite/rewrite.yml +++ b/rewrite/src/main/resources/META-INF/rewrite/rewrite.yml @@ -3,6 +3,7 @@ name: dev.morphia.UpgradeToMorphia30 displayName: Upgrade to Morphia 3.0 description: Adopt new dependencies and breaking changes in moving to Morphia 3. recipeList: + - dev.morphia.rewrite.recipes.UnwrapFieldExpressions - dev.morphia.rewrite.recipes.PipelineRewrite - dev.morphia.rewrite.recipes.CreateDatastoreMigration - dev.morphia.rewrite.recipes.MorphiaConfigMigration @@ -26,10 +27,16 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: dev.morphia.mapping.MapperOptions.PropertyDiscovery newFullyQualifiedTypeName: dev.morphia.mapping.PropertyDiscovery + - org.openrewrite.java.RemoveMethodInvocations: + methodPattern: dev.morphia.Datastore enableDocumentValidation(..) - org.openrewrite.java.RemoveMethodInvocations: methodPattern: dev.morphia.MorphiaDatastore enableDocumentValidation(..) + - org.openrewrite.java.RemoveMethodInvocations: + methodPattern: dev.morphia.Datastore enableCaps(..) - org.openrewrite.java.RemoveMethodInvocations: methodPattern: dev.morphia.MorphiaDatastore enableCaps(..) + - org.openrewrite.java.RemoveMethodInvocations: + methodPattern: dev.morphia.Datastore ensureIndexes(..) - org.openrewrite.java.RemoveMethodInvocations: methodPattern: dev.morphia.MorphiaDatastore ensureIndexes(..) - dev.morphia.rewrite.recipes.RenameMethod: @@ -64,8 +71,21 @@ recipeList: - dev.morphia.test.query.TestQuery check(dev.morphia.query.Query) - dev.morphia.test.TestUpdateOperations testUpsert() - dev.morphia.test.TestUpdateOperations doUpdates(..) + - dev.morphia.test.mapping.TestPropertyModel arrayFieldMapping() + - dev.morphia.test.mapping.TestPropertyModel collectionFieldMapping() + - dev.morphia.test.mapping.TestPropertyModel nestedCollectionsMapping() + - dev.morphia.test.mapping.TestPropertyModel nestedGenerics() + - dev.morphia.test.mapping.codec.TestDocumentWriter testAnd() + - dev.morphia.test.mapping.codec.TestDocumentWriter testOr() + - dev.morphia.test.query.TestQueriesOnReferences testWithKeyQuery() - dev.morphia.rewrite.recipes.RenameMethod: methodPatterns: - dev.morphia.mapping.codec.pojo.EntityModel getCollectionName() collectionName - dev.morphia.mapping.codec.pojo.EntityModel getDiscriminator() discriminator - dev.morphia.mapping.codec.pojo.EntityModel getDiscriminatorKey() discriminatorKey + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: dev.morphia.aggregation.codecs.ExpressionHelper + newFullyQualifiedTypeName: dev.morphia.mapping.codec.CodecHelper + - org.openrewrite.java.ChangeType: + oldFullyQualifiedTypeName: dev.morphia.Datastore + newFullyQualifiedTypeName: dev.morphia.MorphiaDatastore diff --git a/rewrite/src/test/java/dev/morphia/rewrite/recipes/test/PipelineRewriteTest.java b/rewrite/src/test/java/dev/morphia/rewrite/recipes/test/PipelineRewriteTest.java index e945153514a..2f063bf337d 100644 --- a/rewrite/src/test/java/dev/morphia/rewrite/recipes/test/PipelineRewriteTest.java +++ b/rewrite/src/test/java/dev/morphia/rewrite/recipes/test/PipelineRewriteTest.java @@ -137,4 +137,42 @@ public Aggregation testWhitespace() { }""")); } + @Test + public void testMatch() { + rewriteRun(java( + """ + import dev.morphia.Datastore; + import dev.morphia.query.MorphiaCursor; + import dev.morphia.query.filters.Filters; + import org.bson.Document; + + public class UnwrapTest { + public MorphiaCursor update(Datastore ds) { + Object e2 = ds.aggregate(Object.class) + .match(Filters.eq("reference", "ec")) + .execute(Object.class) + .tryNext(); + } + } + """, + """ + import dev.morphia.Datastore; + import dev.morphia.query.MorphiaCursor; + import dev.morphia.query.filters.Filters; + import org.bson.Document; + + import static dev.morphia.aggregation.stages.Match.match; + + public class UnwrapTest { + public MorphiaCursor update(Datastore ds) { + Object e2 =# + ds.aggregate(Object.class) + .pipeline( + match(Filters.eq("reference", "ec"))) + .execute(Object.class) + .tryNext(); + } + } + """.replace('#', ' '))); + } }