Browse Source
fix issues with property path vs mongo path mapping by introducing segments that can represent various combinationsissue/4516
4 changed files with 635 additions and 101 deletions
@ -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…
Reference in new issue