Skip to content

Commit b46aab2

Browse files
christophstroblmp911de
authored andcommitted
Map collection and fields for $graphLookup aggregation against type.
This commit enables using a type parameter to define the from collection of a graphLookup aggregation stage. In doing so we can derive the target collection name from the type and use the given information to also map the from field against the domain object to so that the user is able to operate on property names instead of the target db field name.
1 parent ff137ec commit b46aab2

File tree

2 files changed

+94
-9
lines changed

2 files changed

+94
-9
lines changed

Diff for: spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperation.java

+37-8
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
4646
private static final Set<Class<?>> ALLOWED_START_TYPES = new HashSet<Class<?>>(
4747
Arrays.<Class<?>> asList(AggregationExpression.class, String.class, Field.class, Document.class));
4848

49-
private final String from;
49+
private final Object from;
5050
private final List<Object> startWith;
5151
private final Field connectFrom;
5252
private final Field connectTo;
@@ -55,7 +55,7 @@ public class GraphLookupOperation implements InheritsFieldsAggregationOperation
5555
private final @Nullable Field depthField;
5656
private final @Nullable CriteriaDefinition restrictSearchWithMatch;
5757

58-
private GraphLookupOperation(String from, List<Object> startWith, Field connectFrom, Field connectTo, Field as,
58+
private GraphLookupOperation(Object from, List<Object> startWith, Field connectFrom, Field connectTo, Field as,
5959
@Nullable Long maxDepth, @Nullable Field depthField, @Nullable CriteriaDefinition restrictSearchWithMatch) {
6060

6161
this.from = from;
@@ -82,7 +82,7 @@ public Document toDocument(AggregationOperationContext context) {
8282

8383
Document graphLookup = new Document();
8484

85-
graphLookup.put("from", from);
85+
graphLookup.put("from", getCollectionName(context));
8686

8787
List<Object> mappedStartWith = new ArrayList<>(startWith.size());
8888

@@ -99,7 +99,7 @@ public Document toDocument(AggregationOperationContext context) {
9999

100100
graphLookup.put("startWith", mappedStartWith.size() == 1 ? mappedStartWith.iterator().next() : mappedStartWith);
101101

102-
graphLookup.put("connectFromField", connectFrom.getTarget());
102+
graphLookup.put("connectFromField", getForeignFieldName(context));
103103
graphLookup.put("connectToField", connectTo.getTarget());
104104
graphLookup.put("as", as.getName());
105105

@@ -118,6 +118,16 @@ public Document toDocument(AggregationOperationContext context) {
118118
return new Document(getOperator(), graphLookup);
119119
}
120120

121+
String getCollectionName(AggregationOperationContext context) {
122+
return from instanceof Class<?> type ? context.getCollection(type) : from.toString();
123+
}
124+
125+
String getForeignFieldName(AggregationOperationContext context) {
126+
127+
return from instanceof Class<?> type ? context.getMappedFieldName(type, connectFrom.getTarget())
128+
: connectFrom.getTarget();
129+
}
130+
121131
@Override
122132
public String getOperator() {
123133
return "$graphLookup";
@@ -128,7 +138,7 @@ public ExposedFields getFields() {
128138

129139
List<ExposedField> fields = new ArrayList<>(2);
130140
fields.add(new ExposedField(as, true));
131-
if(depthField != null) {
141+
if (depthField != null) {
132142
fields.add(new ExposedField(depthField, true));
133143
}
134144
return ExposedFields.from(fields.toArray(new ExposedField[0]));
@@ -146,6 +156,17 @@ public interface FromBuilder {
146156
* @return never {@literal null}.
147157
*/
148158
StartWithBuilder from(String collectionName);
159+
160+
/**
161+
* Use the given type to determine name of the foreign collection and map
162+
* {@link ConnectFromBuilder#connectFrom(String)} against it to consider eventually present
163+
* {@link org.springframework.data.mongodb.core.mapping.Field} annotations.
164+
*
165+
* @param type must not be {@literal null}.
166+
* @return never {@literal null}.
167+
* @since 4.2
168+
*/
169+
StartWithBuilder from(Class<?> type);
149170
}
150171

151172
/**
@@ -218,7 +239,7 @@ public interface ConnectToBuilder {
218239
static final class GraphLookupOperationFromBuilder
219240
implements FromBuilder, StartWithBuilder, ConnectFromBuilder, ConnectToBuilder {
220241

221-
private @Nullable String from;
242+
private @Nullable Object from;
222243
private @Nullable List<? extends Object> startWith;
223244
private @Nullable String connectFrom;
224245

@@ -231,6 +252,14 @@ public StartWithBuilder from(String collectionName) {
231252
return this;
232253
}
233254

255+
@Override
256+
public StartWithBuilder from(Class<?> type) {
257+
258+
Assert.notNull(type, "Type must not be null");
259+
this.from = type;
260+
return this;
261+
}
262+
234263
@Override
235264
public ConnectFromBuilder startWith(String... fieldReferences) {
236265

@@ -321,15 +350,15 @@ public GraphLookupOperationBuilder connectTo(String fieldName) {
321350
*/
322351
public static final class GraphLookupOperationBuilder {
323352

324-
private final String from;
353+
private final Object from;
325354
private final List<Object> startWith;
326355
private final Field connectFrom;
327356
private final Field connectTo;
328357
private @Nullable Long maxDepth;
329358
private @Nullable Field depthField;
330359
private @Nullable CriteriaDefinition restrictSearchWithMatch;
331360

332-
protected GraphLookupOperationBuilder(String from, List<? extends Object> startWith, String connectFrom,
361+
protected GraphLookupOperationBuilder(Object from, List<? extends Object> startWith, String connectFrom,
333362
String connectTo) {
334363

335364
this.from = from;

Diff for: spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/GraphLookupOperationUnitTests.java

+57-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.bson.Document;
2323
import org.junit.jupiter.api.Test;
2424
import org.springframework.data.mongodb.core.Person;
25+
import org.springframework.data.mongodb.core.mapping.Field;
2526
import org.springframework.data.mongodb.core.query.Criteria;
2627

2728
/**
@@ -34,7 +35,7 @@ public class GraphLookupOperationUnitTests {
3435

3536
@Test // DATAMONGO-1551
3637
public void rejectsNullFromCollection() {
37-
assertThatIllegalArgumentException().isThrownBy(() -> GraphLookupOperation.builder().from(null));
38+
assertThatIllegalArgumentException().isThrownBy(() -> GraphLookupOperation.builder().from((String) null));
3839
}
3940

4041
@Test // DATAMONGO-1551
@@ -158,4 +159,59 @@ public void depthFieldShouldUseTargetFieldInsteadOfAlias() {
158159

159160
assertThat(document).containsEntry("$graphLookup.depthField", "foo.bar");
160161
}
162+
163+
@Test // GH-4379
164+
void unmappedLookupWithFromExtractedFromType() {
165+
166+
GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() //
167+
.from(Employee.class) //
168+
.startWith(LiteralOperators.Literal.asLiteral("hello")) //
169+
.connectFrom("manager") //
170+
.connectTo("name") //
171+
.as("reportingHierarchy");
172+
173+
assertThat(graphLookupOperation.toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo("""
174+
{ $graphLookup:
175+
{
176+
from: "employee",
177+
startWith : { $literal : "hello" },
178+
connectFromField: "manager",
179+
connectToField: "name",
180+
as: "reportingHierarchy"
181+
}
182+
}}
183+
""");
184+
}
185+
186+
@Test // GH-4379
187+
void mappedLookupWithFromExtractedFromType() {
188+
189+
GraphLookupOperation graphLookupOperation = GraphLookupOperation.builder() //
190+
.from(Employee.class) //
191+
.startWith(LiteralOperators.Literal.asLiteral("hello")) //
192+
.connectFrom("manager") //
193+
.connectTo("name") //
194+
.as("reportingHierarchy");
195+
196+
assertThat(graphLookupOperation.toDocument(AggregationTestUtils.strict(Employee.class).ctx())).isEqualTo("""
197+
{ $graphLookup:
198+
{
199+
from: "employees",
200+
startWith : { $literal : "hello" },
201+
connectFromField: "reportsTo",
202+
connectToField: "name",
203+
as: "reportingHierarchy"
204+
}
205+
}}
206+
""");
207+
}
208+
209+
@org.springframework.data.mongodb.core.mapping.Document("employees")
210+
static class Employee {
211+
212+
String id;
213+
214+
@Field("reportsTo")
215+
String manager;
216+
}
161217
}

0 commit comments

Comments
 (0)