diff --git a/core/src/main/java/dev/morphia/aggregation/codecs/stages/GraphLookupCodec.java b/core/src/main/java/dev/morphia/aggregation/codecs/stages/GraphLookupCodec.java index 359eb0800c7..ab50b9a8329 100644 --- a/core/src/main/java/dev/morphia/aggregation/codecs/stages/GraphLookupCodec.java +++ b/core/src/main/java/dev/morphia/aggregation/codecs/stages/GraphLookupCodec.java @@ -5,6 +5,7 @@ import dev.morphia.query.filters.Filter; import org.bson.BsonWriter; +import org.bson.codecs.Codec; import org.bson.codecs.EncoderContext; import org.bson.codecs.configuration.CodecRegistry; @@ -22,6 +23,7 @@ public Class getEncoderClass() { return GraphLookup.class; } + @SuppressWarnings({ "unchecked", "rawtypes" }) @Override protected void encodeStage(BsonWriter writer, GraphLookup value, EncoderContext encoderContext) { document(writer, () -> { @@ -41,7 +43,8 @@ protected void encodeStage(BsonWriter writer, GraphLookup value, EncoderContext if (restriction != null) { document(writer, "restrictSearchWithMatch", () -> { for (Filter filter : restriction) { - filter.encode(getDatastore(), writer, encoderContext); + Codec codec = getCodecRegistry().get(filter.getClass()); + codec.encode(writer, filter, encoderContext); } }); } diff --git a/core/src/main/java/dev/morphia/mapping/codec/CodecHelper.java b/core/src/main/java/dev/morphia/mapping/codec/CodecHelper.java index bf62f38e5d6..9fae22bf174 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/CodecHelper.java +++ b/core/src/main/java/dev/morphia/mapping/codec/CodecHelper.java @@ -7,6 +7,7 @@ import com.mongodb.lang.NonNull; import com.mongodb.lang.Nullable; +import dev.morphia.MorphiaDatastore; import dev.morphia.aggregation.expressions.impls.Expression; import dev.morphia.aggregation.expressions.impls.SingleValuedExpression; import dev.morphia.annotations.internal.MorphiaInternal; @@ -138,4 +139,35 @@ public static void value(CodecRegistry codecRegistry, BsonWriter writer, String encoderContext.encodeWithChildContext(codec, writer, value); } } + + /** + * @hidden + * @morphia.internal + */ + @MorphiaInternal + public static void namedValue(BsonWriter writer, MorphiaDatastore datastore, @Nullable String name, @Nullable Object value, + EncoderContext encoderContext) { + writer.writeName(name); + if (value != null) { + Codec codec = datastore.getCodecRegistry().get(value.getClass()); + encoderContext.encodeWithChildContext(codec, writer, value); + } else { + writer.writeNull(); + } + } + + /** + * @hidden + * @morphia.internal + */ + @MorphiaInternal + public static void unnamedValue(BsonWriter writer, MorphiaDatastore datastore, @Nullable Object value, + EncoderContext encoderContext) { + if (value != null) { + Codec codec = datastore.getCodecRegistry().get(value.getClass()); + encoderContext.encodeWithChildContext(codec, writer, value); + } else { + writer.writeNull(); + } + } } diff --git a/core/src/main/java/dev/morphia/mapping/codec/ModFilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/ModFilterCodec.java new file mode 100644 index 00000000000..da566e63af3 --- /dev/null +++ b/core/src/main/java/dev/morphia/mapping/codec/ModFilterCodec.java @@ -0,0 +1,33 @@ +package dev.morphia.mapping.codec; + +import dev.morphia.MorphiaDatastore; +import dev.morphia.mapping.codec.filters.BaseFilterCodec; +import dev.morphia.query.filters.ModFilter; + +import org.bson.BsonWriter; +import org.bson.codecs.EncoderContext; + +import static dev.morphia.mapping.codec.CodecHelper.array; +import static dev.morphia.mapping.codec.CodecHelper.document; +import static dev.morphia.mapping.codec.CodecHelper.unnamedValue; + +public class ModFilterCodec extends BaseFilterCodec { + public ModFilterCodec(MorphiaDatastore datastore) { + super(datastore); + } + + @Override + public void encode(BsonWriter writer, ModFilter filter, EncoderContext context) { + document(writer, filter.path(datastore.getMapper()), () -> { + array(writer, filter.getName(), () -> { + unnamedValue(writer, datastore, filter.divisor(), context); + unnamedValue(writer, datastore, filter.remainder(), context); + }); + }); + } + + @Override + public Class getEncoderClass() { + return ModFilter.class; + } +} diff --git a/core/src/main/java/dev/morphia/mapping/codec/MorphiaFilterCodecProvider.java b/core/src/main/java/dev/morphia/mapping/codec/MorphiaFilterCodecProvider.java index f11986789f7..6060fdc4706 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/MorphiaFilterCodecProvider.java +++ b/core/src/main/java/dev/morphia/mapping/codec/MorphiaFilterCodecProvider.java @@ -43,6 +43,7 @@ public MorphiaFilterCodecProvider(MorphiaDatastore datastore) { addCodec(new GeoIntersectsFilterCodec(datastore)); addCodec(new JsonSchemaFilterCodec(datastore)); addCodec(new LogicalFilterCodec(datastore)); + addCodec(new ModFilterCodec(datastore)); addCodec(new NearFilterCodec(datastore)); addCodec(new PolygonFilterCodec(datastore)); addCodec(new RegexFilterCodec(datastore)); diff --git a/core/src/main/java/dev/morphia/mapping/codec/filters/BaseFilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/filters/BaseFilterCodec.java index 5ddfbd0faf1..642e360b0bf 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/filters/BaseFilterCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/filters/BaseFilterCodec.java @@ -1,17 +1,12 @@ package dev.morphia.mapping.codec.filters; -import com.mongodb.lang.Nullable; - import dev.morphia.MorphiaDatastore; -import dev.morphia.annotations.internal.MorphiaInternal; import dev.morphia.query.filters.Filter; import dev.morphia.sofia.Sofia; import org.bson.BsonReader; -import org.bson.BsonWriter; import org.bson.codecs.Codec; import org.bson.codecs.DecoderContext; -import org.bson.codecs.EncoderContext; public abstract class BaseFilterCodec implements Codec { protected MorphiaDatastore datastore; @@ -25,34 +20,4 @@ public final T decode(BsonReader reader, DecoderContext decoderContext) { throw new UnsupportedOperationException(Sofia.encodingOnly()); } - /** - * @hidden - * @morphia.internal - */ - @MorphiaInternal - protected void writeNamedValue(@Nullable String name, @Nullable Object value, MorphiaDatastore datastore, BsonWriter writer, - EncoderContext encoderContext) { - writer.writeName(name); - if (value != null) { - Codec codec = datastore.getCodecRegistry().get(value.getClass()); - encoderContext.encodeWithChildContext(codec, writer, value); - } else { - writer.writeNull(); - } - } - - /** - * @hidden - * @morphia.internal - */ - @MorphiaInternal - protected void writeUnnamedValue(@Nullable Object value, MorphiaDatastore datastore, BsonWriter writer, EncoderContext encoderContext) { - if (value != null) { - Codec codec = datastore.getCodecRegistry().get(value.getClass()); - encoderContext.encodeWithChildContext(codec, writer, value); - } else { - writer.writeNull(); - } - } - } diff --git a/core/src/main/java/dev/morphia/mapping/codec/filters/EqFilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/filters/EqFilterCodec.java index bf2cd810cfb..329a470d710 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/filters/EqFilterCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/filters/EqFilterCodec.java @@ -1,6 +1,7 @@ package dev.morphia.mapping.codec.filters; import dev.morphia.MorphiaDatastore; +import dev.morphia.mapping.codec.CodecHelper; import dev.morphia.query.filters.EqFilter; import org.bson.BsonWriter; @@ -19,11 +20,11 @@ public void encode(BsonWriter writer, EqFilter filter, EncoderContext encoderCon document(writer, filter.path(datastore.getMapper()), () -> { document(writer, "$not", () -> { writer.writeName(filter.getName()); - writeUnnamedValue(filter.getValue(datastore), datastore, writer, encoderContext); + CodecHelper.unnamedValue(writer, datastore, filter.getValue(datastore), encoderContext); }); }); } else { - writeNamedValue(filter.path(datastore.getMapper()), filter.getValue(datastore), datastore, writer, encoderContext); + CodecHelper.namedValue(writer, datastore, filter.path(datastore.getMapper()), filter.getValue(datastore), encoderContext); } } diff --git a/core/src/main/java/dev/morphia/mapping/codec/filters/FilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/filters/FilterCodec.java index 354a854ee10..d05e9d461a9 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/filters/FilterCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/filters/FilterCodec.java @@ -1,6 +1,7 @@ package dev.morphia.mapping.codec.filters; import dev.morphia.MorphiaDatastore; +import dev.morphia.mapping.codec.CodecHelper; import dev.morphia.query.filters.Filter; import org.bson.BsonWriter; @@ -18,10 +19,10 @@ public void encode(BsonWriter writer, Filter filter, EncoderContext encoderConte document(writer, filter.path(datastore.getMapper()), () -> { if (filter.isNot()) { document(writer, "$not", () -> { - writeNamedValue(filter.getName(), filter.getValue(datastore), datastore, writer, encoderContext); + CodecHelper.namedValue(writer, datastore, filter.getName(), filter.getValue(datastore), encoderContext); }); } else { - writeNamedValue(filter.getName(), filter.getValue(datastore), datastore, writer, encoderContext); + CodecHelper.namedValue(writer, datastore, filter.getName(), filter.getValue(datastore), encoderContext); } }); } diff --git a/core/src/main/java/dev/morphia/mapping/codec/filters/NearFilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/filters/NearFilterCodec.java index 743ecea1f4c..333b627a17f 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/filters/NearFilterCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/filters/NearFilterCodec.java @@ -1,6 +1,7 @@ package dev.morphia.mapping.codec.filters; import dev.morphia.MorphiaDatastore; +import dev.morphia.mapping.codec.CodecHelper; import dev.morphia.query.filters.NearFilter; import org.bson.BsonWriter; @@ -31,7 +32,7 @@ public void encode(BsonWriter writer, NearFilter near, EncoderContext encoderCon private void encodeFilter(BsonWriter writer, NearFilter near, EncoderContext context) { document(writer, near.getName(), () -> { writer.writeName("$geometry"); - writeUnnamedValue(near.getValue(datastore), datastore, writer, context); + CodecHelper.unnamedValue(writer, datastore, near.getValue(datastore), context); value(writer, "$maxDistance", near.maxDistance()); value(writer, "$minDistance", near.minDistance()); value(datastore.getCodecRegistry(), writer, "crs", near.crs(), context); diff --git a/core/src/main/java/dev/morphia/mapping/codec/filters/SampleRateFilterCodec.java b/core/src/main/java/dev/morphia/mapping/codec/filters/SampleRateFilterCodec.java index 7dabb5cbc94..b4cb85d06cb 100644 --- a/core/src/main/java/dev/morphia/mapping/codec/filters/SampleRateFilterCodec.java +++ b/core/src/main/java/dev/morphia/mapping/codec/filters/SampleRateFilterCodec.java @@ -2,6 +2,7 @@ import dev.morphia.MorphiaDatastore; import dev.morphia.aggregation.expressions.SampleRateFilter; +import dev.morphia.mapping.codec.CodecHelper; import org.bson.BsonWriter; import org.bson.codecs.EncoderContext; @@ -13,7 +14,7 @@ public SampleRateFilterCodec(MorphiaDatastore datastore) { @Override public void encode(BsonWriter writer, SampleRateFilter filter, EncoderContext encoderContext) { - writeNamedValue(filter.getName(), filter.getValue(), datastore, writer, encoderContext); + CodecHelper.namedValue(writer, datastore, filter.getName(), filter.getValue(), encoderContext); } @Override diff --git a/core/src/main/java/dev/morphia/query/filters/Filter.java b/core/src/main/java/dev/morphia/query/filters/Filter.java index a1e60a4717c..1ae8294c2b6 100644 --- a/core/src/main/java/dev/morphia/query/filters/Filter.java +++ b/core/src/main/java/dev/morphia/query/filters/Filter.java @@ -63,18 +63,6 @@ public boolean isNot() { return not; } - /** - * @param datastore the datastore - * @param writer the writer - * @param context the context - * @hidden - * @morphia.internal - */ - @Deprecated - public void encode(MorphiaDatastore datastore, BsonWriter writer, EncoderContext context) { - throw new UnsupportedOperationException(); - } - /** * Sets the query entity type on the filter * diff --git a/core/src/main/java/dev/morphia/query/filters/GeoWithinFilter.java b/core/src/main/java/dev/morphia/query/filters/GeoWithinFilter.java index e5ed432b079..6ebff2732a7 100644 --- a/core/src/main/java/dev/morphia/query/filters/GeoWithinFilter.java +++ b/core/src/main/java/dev/morphia/query/filters/GeoWithinFilter.java @@ -4,13 +4,8 @@ import com.mongodb.client.model.geojson.MultiPolygon; import com.mongodb.client.model.geojson.Polygon; -import dev.morphia.MorphiaDatastore; import dev.morphia.annotations.internal.MorphiaInternal; -import org.bson.BsonWriter; -import org.bson.codecs.Codec; -import org.bson.codecs.EncoderContext; - /** * Defines a $geoWithin filter. * @@ -49,19 +44,4 @@ public GeoWithinFilter crs(CoordinateReferenceSystem crs) { return this; } - @Override - public final void encode(MorphiaDatastore datastore, BsonWriter writer, EncoderContext context) { - writer.writeStartDocument(path(datastore.getMapper())); - writer.writeStartDocument(getName()); - writer.writeName("$geometry"); - - Object shape = getValue(); - if (shape != null) { - Codec codec = datastore.getCodecRegistry().get(shape.getClass()); - codec.encode(writer, shape, context); - } - - writer.writeEndDocument(); - writer.writeEndDocument(); - } } diff --git a/core/src/main/java/dev/morphia/query/filters/ModFilter.java b/core/src/main/java/dev/morphia/query/filters/ModFilter.java index 30169f9e9d3..8cd60622244 100644 --- a/core/src/main/java/dev/morphia/query/filters/ModFilter.java +++ b/core/src/main/java/dev/morphia/query/filters/ModFilter.java @@ -1,11 +1,13 @@ package dev.morphia.query.filters; -import dev.morphia.MorphiaDatastore; +import dev.morphia.annotations.internal.MorphiaInternal; -import org.bson.BsonWriter; -import org.bson.codecs.EncoderContext; - -class ModFilter extends Filter { +/** + * @hidden + * @morphia.internal + */ +@MorphiaInternal +public class ModFilter extends Filter { private final long divisor; private final long remainder; @@ -16,14 +18,23 @@ public ModFilter(String field, long divisor, long remainder) { this.remainder = remainder; } - @Override - public void encode(MorphiaDatastore datastore, BsonWriter writer, EncoderContext context) { - writer.writeStartDocument(path(datastore.getMapper())); - writer.writeName(getName()); - writer.writeStartArray(); - writeValue(divisor, datastore, writer, context); - writeValue(remainder, datastore, writer, context); - writer.writeEndArray(); - writer.writeEndDocument(); + /** + * @hidden + * @morphia.internal + * @return the divisor + */ + @MorphiaInternal + public long divisor() { + return divisor; + } + + /** + * @hidden + * @morphia.internal + * @return the remainder + */ + @MorphiaInternal + public long remainder() { + return remainder; } } diff --git a/core/src/test/java/dev/morphia/test/TemplatedTestBase.java b/core/src/test/java/dev/morphia/test/TemplatedTestBase.java index 58b28252a7d..3013dc69f33 100644 --- a/core/src/test/java/dev/morphia/test/TemplatedTestBase.java +++ b/core/src/test/java/dev/morphia/test/TemplatedTestBase.java @@ -51,7 +51,7 @@ public TemplatedTestBase(MorphiaConfig config) { } public final String prefix() { - String root = getClass().getSimpleName().substring(4); + String root = getClass().getSimpleName().replace("Test", ""); return toLowerCase(root.charAt(0)) + root.substring(1); } diff --git a/core/src/test/java/dev/morphia/test/TestBase.java b/core/src/test/java/dev/morphia/test/TestBase.java index d18d8f86cf5..aefa1683b41 100644 --- a/core/src/test/java/dev/morphia/test/TestBase.java +++ b/core/src/test/java/dev/morphia/test/TestBase.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.function.Supplier; +import java.util.stream.Collectors; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; @@ -32,6 +33,7 @@ import org.bson.codecs.EncoderContext; import org.bson.codecs.configuration.CodecProvider; import org.bson.codecs.configuration.CodecRegistry; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -42,6 +44,7 @@ import static java.nio.file.Files.lines; import static java.util.stream.Collectors.toList; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertSame; import static org.testng.Assert.fail; public abstract class TestBase extends MorphiaTestSetup { @@ -251,14 +254,13 @@ private void assertDocumentEquals(String path, Object actual, Object expected) { if (expected == null) { return; } - assertSameType(path, actual, expected); if (expected instanceof Document) { for (Entry entry : ((Document) expected).entrySet()) { final String key = entry.getKey(); Object expectedValue = entry.getValue(); Object actualValue = ((Document) actual).get(key); - assertDocumentEquals(path + "." + key, actualValue, expectedValue); + assertDocumentEquals(append(path, key), actualValue, expectedValue); } } else if (expected instanceof List) { List list = (List) expected; @@ -269,9 +271,10 @@ private void assertDocumentEquals(String path, Object actual, Object expected) { o = list.get(i); boolean found = false; final Iterator other = copy.iterator(); + String newPath = null; while (!found && other.hasNext()) { try { - String newPath = format("%s[%d]", path, i); + newPath = format("%s[%d]", path, i); assertDocumentEquals(newPath, other.next(), o); other.remove(); found = true; @@ -279,14 +282,23 @@ private void assertDocumentEquals(String path, Object actual, Object expected) { } } if (!found) { - fail(format("mismatch found at %s", path)); + fail("mismatch found at %s.\n\tactual = %s,\n\texpected = %s".formatted(newPath, actual, expected)); } } } else { - assertEquals(actual, expected, format("mismatch found at %s:%n%s vs %s", path, expected, actual)); + assertEquals(coerceToLong(actual), coerceToLong(expected), format("mismatch found at %s:%n%s vs %s", path, expected, actual)); } } + @NotNull + private static String append(String path, String key) { + return path.isEmpty() ? key: path + "." + key; + } + + private static Object coerceToLong(Object object) { + return object instanceof Integer ? ((Integer) object).longValue() : object; + } + private void assertSameNullity(String path, Object expected, Object actual) { if (expected == null && actual != null || actual == null && expected != null) { diff --git a/core/src/test/java/dev/morphia/test/query/FiltersTest.java b/core/src/test/java/dev/morphia/test/query/FiltersTest.java index 56eef6bdafb..9e9db25d142 100644 --- a/core/src/test/java/dev/morphia/test/query/FiltersTest.java +++ b/core/src/test/java/dev/morphia/test/query/FiltersTest.java @@ -13,9 +13,11 @@ import dev.morphia.annotations.Id; import dev.morphia.query.FindOptions; import dev.morphia.query.Meta; +import dev.morphia.query.MorphiaQuery; import dev.morphia.query.Query; import dev.morphia.query.Type; -import dev.morphia.test.TestBase; +import dev.morphia.test.TemplatedTestBase; +import dev.morphia.test.aggregation.model.Inventory; import dev.morphia.test.models.Budget; import dev.morphia.test.models.User; @@ -40,6 +42,7 @@ import static dev.morphia.query.filters.Filters.jsonSchema; import static dev.morphia.query.filters.Filters.lt; import static dev.morphia.query.filters.Filters.lte; +import static dev.morphia.query.filters.Filters.mod; import static dev.morphia.query.filters.Filters.nin; import static dev.morphia.query.filters.Filters.nor; import static dev.morphia.query.filters.Filters.or; @@ -55,7 +58,7 @@ import static org.testng.Assert.assertTrue; @SuppressWarnings("resource") -public class FiltersTest extends TestBase { +public class FiltersTest extends TemplatedTestBase { @Test public void testAnd() { getDs().find(Budget.class) @@ -260,6 +263,15 @@ public void testMeta() { } + @Test + public void testMod() { + var query = getDs().find(Inventory.class) + .disableValidation() + .filter(mod("qty", 4, 0)); + testQuery((MorphiaQuery) query, new FindOptions(), true); + + } + @Test public void testNin() { getDs().find(Budget.class) diff --git a/core/src/test/resources/dev/morphia/test/query/filters/mod/data.json b/core/src/test/resources/dev/morphia/test/query/filters/mod/data.json new file mode 100644 index 00000000000..501ad468365 --- /dev/null +++ b/core/src/test/resources/dev/morphia/test/query/filters/mod/data.json @@ -0,0 +1,3 @@ +{ "_id" : 1, "item" : "abc123", "qty" : 0 } +{ "_id" : 2, "item" : "xyz123", "qty" : 5 } +{ "_id" : 3, "item" : "ijk123", "qty" : 12 } \ No newline at end of file diff --git a/core/src/test/resources/dev/morphia/test/query/filters/mod/expected.json b/core/src/test/resources/dev/morphia/test/query/filters/mod/expected.json new file mode 100644 index 00000000000..504bcd1519b --- /dev/null +++ b/core/src/test/resources/dev/morphia/test/query/filters/mod/expected.json @@ -0,0 +1,2 @@ +{ "_id" : 1, "item" : "abc123", "qty" : 0 } +{ "_id" : 3, "item" : "ijk123", "qty" : 12 } \ No newline at end of file diff --git a/core/src/test/resources/dev/morphia/test/query/filters/mod/query.json b/core/src/test/resources/dev/morphia/test/query/filters/mod/query.json new file mode 100644 index 00000000000..83e6604a8d4 --- /dev/null +++ b/core/src/test/resources/dev/morphia/test/query/filters/mod/query.json @@ -0,0 +1 @@ +{ qty: { $mod: [ 4, 0 ] } } \ No newline at end of file