Browse Source

Hacking: More intermediate types for path mapping

fix issues with property path vs mongo path mapping by introducing segments that can represent various combinations
issue/4516
Christoph Strobl 3 months ago
parent
commit
cb3557a954
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 13
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java
  2. 371
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPath.java
  3. 90
      spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPaths.java
  4. 262
      spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPathsUnitTests.java

13
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/convert/QueryMapper.java

@ -36,7 +36,6 @@ import org.bson.BsonValue; @@ -36,7 +36,6 @@ import org.bson.BsonValue;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.annotation.Reference;
@ -57,14 +56,9 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpression; @@ -57,14 +56,9 @@ import org.springframework.data.mongodb.core.aggregation.AggregationExpression;
import org.springframework.data.mongodb.core.aggregation.RelaxedTypeBasedAggregationOperationContext;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter.NestedDocument;
import org.springframework.data.mongodb.core.mapping.FieldName;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath.MappedSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment;
import org.springframework.data.mongodb.core.mapping.MongoPaths;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath.Segment;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath.TargetType;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
import org.springframework.data.mongodb.core.query.Query;
@ -1140,7 +1134,8 @@ public class QueryMapper { @@ -1140,7 +1134,8 @@ public class QueryMapper {
*/
@Nullable
public PropertyPath toPropertyPath(
MongoPath mongoPath, MongoPersistentEntity<?> persistentEntity) {
MongoPath mongoPath, MongoPersistentEntity<?> persistentEntity) {
StringBuilder path = new StringBuilder();
MongoPersistentEntity<?> entity = persistentEntity;
@ -1184,7 +1179,6 @@ public class QueryMapper { @@ -1184,7 +1179,6 @@ public class QueryMapper {
return PropertyPath.from(path.toString(), persistentEntity.getType());
}
/**
* Extension of {@link Field} to be backed with mapping metadata.
*
@ -1331,7 +1325,6 @@ public class QueryMapper { @@ -1331,7 +1325,6 @@ public class QueryMapper {
return name;
}
@Nullable
protected PersistentPropertyPath<MongoPersistentProperty> getPath() {
return propertyPath;
@ -1351,7 +1344,7 @@ public class QueryMapper { @@ -1351,7 +1344,7 @@ public class QueryMapper {
PropertyPath.from(Pattern.quote(sourceProperty.getName()), entity.getTypeInformation()));
}
PropertyPath path = toPropertyPath(mongoPath, entity);
PropertyPath path = paths.mappedPath(mongoPath, entity.getTypeInformation()).propertyPath();
if (path == null || isPathToJavaLangClassProperty(path)) {
return null;

371
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPath.java

@ -20,10 +20,17 @@ import java.util.LinkedHashMap; @@ -20,10 +20,17 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment.PropertySegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath.Keyword;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ConcurrentLruCache;
import org.springframework.util.ObjectUtils;
@ -50,6 +57,128 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -50,6 +57,128 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
String segment();
static PathSegment of(String segment) {
Keyword keyword = Keyword.mapping.get(segment);
if (keyword != null) {
return new KeywordSegment(keyword, new Segment(segment, false, true));
}
if (PositionSegment.POSITIONAL.matcher(segment).matches()) {
return new PositionSegment(new Segment(segment, true, false));
}
if (segment.startsWith("$")) {
return new KeywordSegment(null, new Segment(segment, false, true));
}
return new PropertySegment(new Segment(segment, false, false));
}
record Segment(String segment, boolean isNumeric, boolean isKeyword) implements PathSegment {
}
class KeywordSegment implements PathSegment {
final @Nullable Keyword keyword;
final Segment segment;
public KeywordSegment(@Nullable Keyword keyword, Segment segment) {
this.keyword = keyword;
this.segment = segment;
}
@Override
public boolean isNumeric() {
return false;
}
@Override
public boolean isKeyword() {
return true;
}
@Override
public String segment() {
return segment.segment();
}
@Override
public String toString() {
return segment();
}
}
class PositionSegment implements PathSegment {
/**
* n numeric position <br />
* $[] all positional operator for update operations, <br />
* $[id] filtered positional operator for update operations, <br />
* $ positional operator for update operations, <br />
* $ projection operator when array index position is unknown <br />
*/
private final static Pattern POSITIONAL = Pattern.compile("\\$\\[[a-zA-Z0-9]*]|\\$|\\d+");
final Segment segment;
public PositionSegment(Segment segment) {
this.segment = segment;
}
@Override
public boolean isNumeric() {
return true;
}
@Override
public boolean isKeyword() {
return false;
}
@Override
public String segment() {
return segment.segment();
}
@Override
public String toString() {
return segment();
}
}
class PropertySegment implements PathSegment {
final Segment segment;
public PropertySegment(Segment segment) {
this.segment = segment;
}
@Override
public boolean isNumeric() {
return false;
}
@Override
public boolean isKeyword() {
return false;
}
@Override
public String segment() {
return segment.segment();
}
@Override
public String toString() {
return segment();
}
}
}
/**
@ -65,13 +194,13 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -65,13 +194,13 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
RawMongoPath::new);
private final String path;
private final List<Segment> segments;
private final List<PathSegment> segments;
private RawMongoPath(String path) {
this(path, segmentsOf(path));
}
RawMongoPath(String path, List<Segment> segments) {
RawMongoPath(String path, List<PathSegment> segments) {
this.path = path;
this.segments = List.copyOf(segments);
@ -89,24 +218,24 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -89,24 +218,24 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
return CACHE.get(path);
}
private static List<Segment> segmentsOf(String path) {
private static List<PathSegment> segmentsOf(String path) {
return segmentsOf(path.split("\\."));
}
private static List<Segment> segmentsOf(String[] rawSegments) {
private static List<PathSegment> segmentsOf(String[] rawSegments) {
List<Segment> segments = new ArrayList<>(rawSegments.length);
List<PathSegment> segments = new ArrayList<>(rawSegments.length);
for (String segment : rawSegments) {
segments.add(Segment.of(segment));
segments.add(PathSegment.of(segment));
}
return segments;
}
public List<Segment> getSegments() {
public List<PathSegment> getSegments() {
return this.segments;
}
public List<Segment> segments() {
public List<PathSegment> segments() {
return this.segments;
}
@ -135,55 +264,8 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -135,55 +264,8 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
return StringUtils.collectionToDelimitedString(segments, ".");
}
public record Segment(String segment, boolean keyword, boolean numeric,
TargetType targetType) implements PathSegment {
private final static Pattern POSITIONAL = Pattern.compile("\\$\\[\\d+]");
static Segment of(String segment) {
Keyword keyword = Keyword.mapping.get(segment);
public enum Keyword {
if (keyword != null) {
return new Segment(segment, true, false, keyword.getType());
}
if (POSITIONAL.matcher(segment).matches()) {
return new Segment(segment, true, false, RawMongoPath.Keyword.$POSITIONAL.getType());
}
try {
// positional paths
Integer.decode(segment);
return new Segment(segment, false, true, RawMongoPath.TargetType.PROPERTY);
} catch (NumberFormatException e) {
}
return new Segment(segment, segment.startsWith("$"), false, RawMongoPath.TargetType.PROPERTY);
}
@Override
public String toString() {
return segment;
}
@Override
public boolean isNumeric() {
return numeric;
}
@Override
public boolean isKeyword() {
return keyword;
}
}
enum Keyword {
$PROJECTION("$", TargetType.PROPERTY), //
$POSITIONAL("$[n]", TargetType.PROPERTY), //
$ALL_POSITIONAL("$[]", TargetType.PROPERTY), //
$IN(TargetType.COLLECTION), //
$NIN(TargetType.COLLECTION), //
$EXISTS(TargetType.BOOLEAN), //
@ -249,16 +331,90 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -249,16 +331,90 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
final class MappedMongoPath implements MongoPath {
private final RawMongoPath source;
private final List<MappedSegment> mappedSegments;
private final TypeInformation<?> type;
private final List<? extends PathSegment> segments;
private final Lazy<PropertyPath> propertyPath = Lazy.of(this::assemblePropertyPath);
private final Lazy<String> mappedPath = Lazy.of(this::assembleMappedPath);
public MappedMongoPath(RawMongoPath source, List<MappedSegment> segments) {
public MappedMongoPath(RawMongoPath source, TypeInformation<?> type, List<? extends PathSegment> segments) {
this.source = source;
this.mappedSegments = segments;
this.type = type;
this.segments = segments;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MappedMongoPath that = (MappedMongoPath) o;
return source.equals(that.source) && type.equals(that.type);
}
@Override
public int hashCode() {
return Objects.hash(source, type);
}
public static MappedMongoPath just(RawMongoPath source) {
return new MappedMongoPath(source, TypeInformation.OBJECT,
source.segments().stream().map(it -> new MappedPropertySegment(it.segment(), it, null)).toList());
}
public @Nullable PropertyPath propertyPath() {
return this.propertyPath.getNullable();
}
private String assembleMappedPath() {
return segments.stream().map(PathSegment::segment).filter(StringUtils::hasText).collect(Collectors.joining("."));
}
private @Nullable PropertyPath assemblePropertyPath() {
StringBuilder path = new StringBuilder();
for (PathSegment segment : segments) {
if (segment instanceof PropertySegment) {
return null;
}
if (segment.isKeyword() || segment.isNumeric()) {
continue;
}
String name = segment.segment();
if (segment instanceof MappedPropertySegment mappedSegment) {
name = mappedSegment.getSource().segment();
} else if (segment instanceof WrappedSegment wrappedSegment) {
if (wrappedSegment.getInner() != null) {
name = wrappedSegment.getOuter().getProperty().getName() + "."
+ wrappedSegment.getInner().getProperty().getName();
} else {
name = wrappedSegment.getOuter().getProperty().getName();
}
}
if (!path.isEmpty()) {
path.append(".");
}
path.append(Pattern.quote(name));
}
if (path.isEmpty()) {
return null;
}
return PropertyPath.from(path.toString(), type);
}
@Override
public String path() {
return StringUtils.collectionToDelimitedString(mappedSegments, ".");
return mappedPath.get();
}
public String sourcePath() {
@ -266,15 +422,76 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -266,15 +422,76 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
}
@Override
public List<MappedSegment> segments() {
return mappedSegments;
@SuppressWarnings("unchecked")
public List<PathSegment> segments() {
return (List<PathSegment>) segments;
}
public String toString() {
return path();
}
public record MappedSegment(PathSegment source, String mappedName) implements PathSegment {
public static class AssociationSegment extends MappedPropertySegment {
public AssociationSegment(String mappedName, PathSegment source, MongoPersistentProperty property) {
super(mappedName, source, property);
}
}
public static class WrappedSegment implements PathSegment {
private final String mappedName;
private final MappedPropertySegment outer;
private final MappedPropertySegment inner;
public WrappedSegment(String mappedName, MappedPropertySegment outer, MappedPropertySegment inner) {
this.mappedName = mappedName;
this.outer = outer;
this.inner = inner;
}
public MappedPropertySegment getInner() {
return inner;
}
public MappedPropertySegment getOuter() {
return outer;
}
@Override
public boolean isNumeric() {
return false;
}
@Override
public boolean isKeyword() {
return false;
}
@Override
public String segment() {
return mappedName;
}
@Override
public String toString() {
return segment();
}
}
public static class MappedPropertySegment implements PathSegment {
PathSegment source;
String mappedName;
MongoPersistentProperty property;
public MappedPropertySegment(String mappedName, PathSegment source, MongoPersistentProperty property) {
this.source = source;
this.mappedName = mappedName;
this.property = property;
}
@Override
public boolean isNumeric() {
@ -291,11 +508,39 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp @@ -291,11 +508,39 @@ public sealed interface MongoPath permits MongoPath.RawMongoPath, MongoPath.Mapp
return mappedName;
}
public boolean isMappedToProperty() {
return property != null;
}
@NonNull
@Override
public String toString() {
return mappedName;
}
public PathSegment getSource() {
return source;
}
public void setSource(PathSegment source) {
this.source = source;
}
public String getMappedName() {
return mappedName;
}
public void setMappedName(String mappedName) {
this.mappedName = mappedName;
}
public MongoPersistentProperty getProperty() {
return property;
}
public void setProperty(MongoPersistentProperty property) {
this.property = property;
}
}
}
}

90
spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/mapping/MongoPaths.java

@ -19,9 +19,11 @@ import java.util.ArrayList; @@ -19,9 +19,11 @@ import java.util.ArrayList;
import java.util.List;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath.MappedSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath.Segment;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath.TargetType;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath.MappedPropertySegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath.WrappedSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.RawMongoPath;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.ConcurrentLruCache;
@ -31,7 +33,8 @@ import org.springframework.util.ConcurrentLruCache; @@ -31,7 +33,8 @@ import org.springframework.util.ConcurrentLruCache;
*/
public class MongoPaths {
private final ConcurrentLruCache<PathAndType, MongoPath.MappedMongoPath> CACHE = new ConcurrentLruCache<>(128, this::mapFieldNames);
private final ConcurrentLruCache<PathAndType, MongoPath.MappedMongoPath> CACHE = new ConcurrentLruCache<>(128,
this::mapFieldNames);
private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
public MongoPaths(MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext) {
@ -42,15 +45,23 @@ public class MongoPaths { @@ -42,15 +45,23 @@ public class MongoPaths {
return MongoPath.RawMongoPath.parse(path);
}
public MongoPath mappedPath(MongoPath path, TypeInformation<?> type) {
public MappedMongoPath mappedPath(MongoPath path, Class<?> type) {
return mappedPath(path, TypeInformation.of(type));
}
public MappedMongoPath mappedPath(MongoPath path, TypeInformation<?> type) {
if (!(path instanceof MongoPath.RawMongoPath rawMongoPath)) {
return path;
if (path instanceof MappedMongoPath mappedPath) {
return mappedPath;
}
if (!mappingContext.hasPersistentEntityFor(type.getType())) {
return path;
MongoPath.RawMongoPath rawMongoPath = (RawMongoPath) path;
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(type);
if (persistentEntity == null) {
return MappedMongoPath.just(rawMongoPath);
}
return CACHE.get(new PathAndType(rawMongoPath, type));
}
@ -60,38 +71,61 @@ public class MongoPaths { @@ -60,38 +71,61 @@ public class MongoPaths {
MongoPath.MappedMongoPath mapFieldNames(PathAndType cacheKey) {
MongoPath.RawMongoPath mongoPath = cacheKey.path();
MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(cacheKey.type());
MongoPersistentEntity<?> root = mappingContext.getPersistentEntity(cacheKey.type());
MongoPersistentEntity<?> persistentEntity = root;
List<MappedSegment> segments = new ArrayList<>(mongoPath.getSegments().size());
List<PathSegment> segments = new ArrayList<>(mongoPath.getSegments().size());
for (Segment segment : mongoPath.getSegments()) {
for (int i = 0; i < mongoPath.getSegments().size(); i++) {
if (persistentEntity != null && !segment.keyword()
&& (segment.targetType() == TargetType.ANY || segment.targetType() == TargetType.PROPERTY)) {
EntityIndexSegment eis = segment(i, mongoPath.getSegments(), persistentEntity);
segments.add(eis.segment());
persistentEntity = eis.entity();
i = eis.index();
}
MongoPersistentProperty persistentProperty = persistentEntity.getPersistentProperty(segment.toString());
return new MongoPath.MappedMongoPath(mongoPath, root.getTypeInformation(), segments);
}
String name = segment.segment();
EntityIndexSegment segment(int index, List<PathSegment> segments, MongoPersistentEntity<?> currentEntity) {
if (persistentProperty != null) {
PathSegment segment = segments.get(index);
MongoPersistentEntity<?> entity = currentEntity;
if (persistentProperty.isEntity()) {
persistentEntity = mappingContext.getPersistentEntity(persistentProperty);
}
if (entity != null && !segment.isKeyword()) {
if (persistentProperty.isUnwrapped()) {
continue;
}
MongoPersistentProperty persistentProperty = entity.getPersistentProperty(segment.segment());
if (persistentProperty != null) {
// if(persistentProperty.isEntity()) {
entity = mappingContext.getPersistentEntity(persistentProperty);
// }
name = persistentProperty.getFieldName();
if (persistentProperty.isUnwrapped()) {
if (segments.size() > index + 1) {
EntityIndexSegment inner = segment(index + 1, segments, entity);
if (inner.segment() instanceof MappedPropertySegment mappedInnerSegment) {
return new EntityIndexSegment(inner.entity(), inner.index(),
new WrappedSegment(mappedInnerSegment.getMappedName(),
new MappedPropertySegment(persistentProperty.findAnnotation(Unwrapped.class).prefix(), segment,
persistentProperty),
mappedInnerSegment));
}
} else {
return new EntityIndexSegment(entity, index, new WrappedSegment("", new MappedPropertySegment(
persistentProperty.findAnnotation(Unwrapped.class).prefix(), segment, persistentProperty), null));
}
}
segments.add(new MappedSegment(segment, name));
} else {
segments.add(new MappedSegment(segment, segment.segment()));
return new EntityIndexSegment(entity, index,
new MappedPropertySegment(persistentProperty.getFieldName(), segment, persistentProperty));
}
}
return new EntityIndexSegment(entity, index, segment);
}
return new MongoPath.MappedMongoPath(mongoPath, segments);
record EntityIndexSegment(MongoPersistentEntity<?> entity, int index, PathSegment segment) {
}
}

262
spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/mapping/MongoPathsUnitTests.java

@ -0,0 +1,262 @@ @@ -0,0 +1,262 @@
/*
* Copyright 2025-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.mongodb.core.mapping;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.mapping.PropertyPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath;
import org.springframework.data.mongodb.core.mapping.MongoPath.MappedMongoPath.MappedPropertySegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment.PositionSegment;
import org.springframework.data.mongodb.core.mapping.MongoPath.PathSegment.PropertySegment;
import org.springframework.data.mongodb.core.mapping.Unwrapped.OnEmpty;
import org.springframework.data.mongodb.test.util.MongoTestMappingContext;
/**
* Unit tests for {@link MongoPaths}
*
* @author Christoph Strobl
*/
class MongoPathsUnitTests {
MongoPaths paths;
MongoTestMappingContext mappingContext;
@BeforeEach
void beforeEach() {
mappingContext = MongoTestMappingContext.newTestContext();
paths = new MongoPaths(mappingContext);
}
@Test // GH-4516
void rawPathCaching() {
MongoPath sourcePath = paths.create("inner.value.num");
MongoPath samePathAgain = paths.create("inner.value.num");
assertThat(sourcePath).isSameAs(samePathAgain);
}
@Test // GH-4516
void mappedPathCaching() {
MongoPath sourcePath = paths.create("inner.value.num");
MappedMongoPath mappedPath = paths.mappedPath(sourcePath, Outer.class);
MappedMongoPath pathMappedAgain = paths.mappedPath(sourcePath, Outer.class);
assertThat(mappedPath).isSameAs(pathMappedAgain) //
.isNotEqualTo(paths.mappedPath(sourcePath, Inner.class));
}
@Test // GH-4516
void simplePath() {
MongoPath mongoPath = paths.create("inner.value.num");
assertThat(mongoPath.segments()).hasOnlyElementsOfType(PathSegment.PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.f_val");
assertThat(mappedMongoPath.segments()).hasOnlyElementsOfType(MappedPropertySegment.class);
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value.num", Outer.class));
}
@Test // GH-4516
void mappedPathWithArrayPosition() {
MongoPath mongoPath = paths.create("inner.valueList.0.num");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.valueList.0.f_val");
assertThat(mappedMongoPath.segments()).hasExactlyElementsOfTypes(MappedPropertySegment.class,
MappedPropertySegment.class, PositionSegment.class, MappedPropertySegment.class);
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.valueList.num", Outer.class));
}
@Test // GH-4516
void mappedPathWithReferenceToNonDomainTypeField() {
MongoPath mongoPath = paths.create("inner.valueList.0.xxx");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.valueList.0.xxx");
assertThat(mappedMongoPath.segments()).hasExactlyElementsOfTypes(MappedPropertySegment.class,
MappedPropertySegment.class, PositionSegment.class, PropertySegment.class);
assertThat(mappedMongoPath.propertyPath()).isNull();
}
@Test // GH-4516
void mappedPathToPropertyWithinUnwrappedUnwrappedProperty() {
MongoPath mongoPath = paths.create("inner.wrapper.v1");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.pre-fix-v_1");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.wrapper.v1", Outer.class));
}
@Test // GH-4516
void mappedPathToUnwrappedProperty() { // eg. for update mapping
MongoPath mongoPath = paths.create("inner.wrapper");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.wrapper", Outer.class));
}
@Test // GH-4516
void justPropertySegments() {
MongoPath mongoPath = paths.create("inner.value");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value", Outer.class));
}
@Test // GH-4516
void withPositionalOperatorForUpdates() {
MongoPath mongoPath = paths.create("inner.value.$");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.$");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value", Outer.class));
}
@Test // GH-4516
void withProjectionOperatorForArray() {
MongoPath mongoPath = paths.create("inner.value.$.num");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.$.f_val");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value.num", Outer.class));
}
@Test // GH-4516
void withAllPositionalOperatorForUpdates() {
MongoPath mongoPath = paths.create("inner.value.$[].num");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.$[].f_val");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value.num", Outer.class));
}
@Test // GH-4516
void withNumericFilteredPositionalOperatorForUpdates() {
MongoPath mongoPath = paths.create("inner.value.$[1].num");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.$[1].f_val");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value.num", Outer.class));
}
@Test // GH-4516
void withFilteredPositionalOperatorForUpdates() {
MongoPath mongoPath = paths.create("inner.value.$[elem].num");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PositionSegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.val.$[elem].f_val");
assertThat(mappedMongoPath.propertyPath()).isEqualTo(PropertyPath.from("inner.value.num", Outer.class));
}
@Test // GH-4516
void unwrappedWithNonDomainTypeAndPathThatPointsToPropertyOfUnwrappedType() {
MongoPath mongoPath = paths.create("inner.wrapper.document.v2");
assertThat(mongoPath.segments()).hasExactlyElementsOfTypes(PropertySegment.class, PropertySegment.class,
PropertySegment.class, PropertySegment.class);
MappedMongoPath mappedMongoPath = paths.mappedPath(mongoPath, Outer.class);
assertThat(mappedMongoPath.path()).isEqualTo("inner.pre-fix-document.v2");
assertThat(mappedMongoPath.propertyPath()).isNull();
}
static class Outer {
String id;
Inner inner;
@DBRef //
Referenced ref;
}
static class Inner {
@Field("val") //
Value value;
@Unwrapped(prefix = "pre-fix-", onEmpty = OnEmpty.USE_NULL) //
Wrapper wrapper;
List<Value> valueList;
}
static class Referenced {
@Id String id;
String value;
}
static class Wrapper {
@Field("v_1") String v1;
String v2;
org.bson.Document document;
}
static class Value {
String s_val;
@Field("f_val") Float num;
}
}
Loading…
Cancel
Save