Skip to content

Commit 91f6137

Browse files
christophstroblmp911de
authored andcommitted
Update $out stage rendering to documented format.
This commit makes sure to use the documented command format when rendering the $out aggregation stage. Original pull request: #4986 Closes #4969
1 parent 4e53fa7 commit 91f6137

File tree

2 files changed

+15
-232
lines changed

2 files changed

+15
-232
lines changed

spring-data-mongodb/src/main/java/org/springframework/data/mongodb/core/aggregation/OutOperation.java

Lines changed: 7 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import org.bson.Document;
1919
import org.jspecify.annotations.Nullable;
20-
import org.springframework.data.mongodb.util.BsonUtils;
20+
2121
import org.springframework.lang.Contract;
2222
import org.springframework.util.Assert;
2323
import org.springframework.util.StringUtils;
@@ -37,33 +37,25 @@ public class OutOperation implements AggregationOperation {
3737

3838
private final @Nullable String databaseName;
3939
private final String collectionName;
40-
private final @Nullable Document uniqueKey;
41-
private final @Nullable OutMode mode;
4240

4341
/**
4442
* @param outCollectionName Collection name to export the results. Must not be {@literal null}.
4543
*/
4644
public OutOperation(String outCollectionName) {
47-
this(null, outCollectionName, null, null);
45+
this(null, outCollectionName);
4846
}
4947

5048
/**
5149
* @param databaseName Optional database name the target collection is located in. Can be {@literal null}.
5250
* @param collectionName Collection name to export the results. Must not be {@literal null}. Can be {@literal null}.
53-
* @param uniqueKey Optional unique key spec identify a document in the to collection for replacement or merge.
54-
* @param mode The mode for merging the aggregation pipeline output with the target collection. Can be
55-
* {@literal null}. {@literal null}.
5651
* @since 2.2
5752
*/
58-
private OutOperation(@Nullable String databaseName, String collectionName, @Nullable Document uniqueKey,
59-
@Nullable OutMode mode) {
53+
private OutOperation(@Nullable String databaseName, String collectionName) {
6054

6155
Assert.notNull(collectionName, "Collection name must not be null");
6256

6357
this.databaseName = databaseName;
6458
this.collectionName = collectionName;
65-
this.uniqueKey = uniqueKey;
66-
this.mode = mode;
6759
}
6860

6961
/**
@@ -76,187 +68,21 @@ private OutOperation(@Nullable String databaseName, String collectionName, @Null
7668
*/
7769
@Contract("_ -> new")
7870
public OutOperation in(@Nullable String database) {
79-
return new OutOperation(database, collectionName, uniqueKey, mode);
80-
}
81-
82-
/**
83-
* Optionally specify the field that uniquely identifies a document in the target collection. <br />
84-
* For convenience the given {@literal key} can either be a single field name or the Json representation of a key
85-
* {@link Document}.
86-
*
87-
* <pre class="code">
88-
*
89-
* // {
90-
* // "field-1" : 1
91-
* // }
92-
* .uniqueKey("field-1")
93-
*
94-
* // {
95-
* // "field-1" : 1,
96-
* // "field-2" : 1
97-
* // }
98-
* .uniqueKey("{ 'field-1' : 1, 'field-2' : 1}")
99-
* </pre>
100-
*
101-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
102-
*
103-
* @param key can be {@literal null}. Server uses {@literal _id} when {@literal null}.
104-
* @return new instance of {@link OutOperation}.
105-
* @since 2.2
106-
*/
107-
@Contract("_ -> new")
108-
public OutOperation uniqueKey(@Nullable String key) {
109-
110-
Document uniqueKey = key == null ? null : BsonUtils.toDocumentOrElse(key, it -> new Document(it, 1));
111-
return new OutOperation(databaseName, collectionName, uniqueKey, mode);
112-
}
113-
114-
/**
115-
* Optionally specify the fields that uniquely identifies a document in the target collection. <br />
116-
*
117-
* <pre class="code">
118-
*
119-
* // {
120-
* // "field-1" : 1
121-
* // "field-2" : 1
122-
* // }
123-
* .uniqueKeyOf(Arrays.asList("field-1", "field-2"))
124-
* </pre>
125-
*
126-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
127-
*
128-
* @param fields must not be {@literal null}.
129-
* @return new instance of {@link OutOperation}.
130-
* @since 2.2
131-
*/
132-
@Contract("_ -> new")
133-
public OutOperation uniqueKeyOf(Iterable<String> fields) {
134-
135-
Assert.notNull(fields, "Fields must not be null");
136-
137-
Document uniqueKey = new Document();
138-
fields.forEach(it -> uniqueKey.append(it, 1));
139-
140-
return new OutOperation(databaseName, collectionName, uniqueKey, mode);
141-
}
142-
143-
/**
144-
* Specify how to merge the aggregation output with the target collection. <br />
145-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
146-
*
147-
* @param mode must not be {@literal null}.
148-
* @return new instance of {@link OutOperation}.
149-
* @since 2.2
150-
*/
151-
@Contract("_ -> new")
152-
public OutOperation mode(OutMode mode) {
153-
154-
Assert.notNull(mode, "Mode must not be null");
155-
return new OutOperation(databaseName, collectionName, uniqueKey, mode);
156-
}
157-
158-
/**
159-
* Replace the target collection. <br />
160-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
161-
*
162-
* @return new instance of {@link OutOperation}.
163-
* @see OutMode#REPLACE_COLLECTION
164-
* @since 2.2
165-
*/
166-
@Contract("-> new")
167-
public OutOperation replaceCollection() {
168-
return mode(OutMode.REPLACE_COLLECTION);
169-
}
170-
171-
/**
172-
* Replace/Upsert documents in the target collection. <br />
173-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
174-
*
175-
* @return new instance of {@link OutOperation}.
176-
* @see OutMode#REPLACE
177-
* @since 2.2
178-
*/
179-
@Contract("-> new")
180-
public OutOperation replaceDocuments() {
181-
return mode(OutMode.REPLACE);
182-
}
183-
184-
/**
185-
* Insert documents to the target collection. <br />
186-
* <strong>NOTE:</strong> Requires MongoDB 4.2 or later.
187-
*
188-
* @return new instance of {@link OutOperation}.
189-
* @see OutMode#INSERT
190-
* @since 2.2
191-
*/
192-
@Contract("-> new")
193-
public OutOperation insertDocuments() {
194-
return mode(OutMode.INSERT);
71+
return new OutOperation(database, collectionName);
19572
}
19673

19774
@Override
19875
public Document toDocument(AggregationOperationContext context) {
19976

200-
if (!requiresMongoDb42Format()) {
201-
return new Document("$out", collectionName);
77+
if (!StringUtils.hasText(databaseName)) {
78+
return new Document(getOperator(), collectionName);
20279
}
20380

204-
Assert.state(mode != null, "Mode must not be null");
205-
206-
Document $out = new Document("to", collectionName) //
207-
.append("mode", mode.getMongoMode());
208-
209-
if (StringUtils.hasText(databaseName)) {
210-
$out.append("db", databaseName);
211-
}
212-
213-
if (uniqueKey != null) {
214-
$out.append("uniqueKey", uniqueKey);
215-
}
216-
217-
return new Document(getOperator(), $out);
81+
return new Document(getOperator(), new Document("db", databaseName).append("coll", collectionName));
21882
}
21983

22084
@Override
22185
public String getOperator() {
22286
return "$out";
22387
}
224-
225-
private boolean requiresMongoDb42Format() {
226-
return StringUtils.hasText(databaseName) || mode != null || uniqueKey != null;
227-
}
228-
229-
/**
230-
* The mode for merging the aggregation pipeline output.
231-
*
232-
* @author Christoph Strobl
233-
* @since 2.2
234-
*/
235-
public enum OutMode {
236-
237-
/**
238-
* Write documents to the target collection. Errors if a document same uniqueKey already exists.
239-
*/
240-
INSERT("insertDocuments"),
241-
242-
/**
243-
* Update on any document in the target collection with the same uniqueKey.
244-
*/
245-
REPLACE("replaceDocuments"),
246-
247-
/**
248-
* Replaces the to collection with the output from the aggregation pipeline. Cannot be in a different database.
249-
*/
250-
REPLACE_COLLECTION("replaceCollection");
251-
252-
private final String mode;
253-
254-
OutMode(String mode) {
255-
this.mode = mode;
256-
}
257-
258-
public String getMongoMode() {
259-
return mode;
260-
}
261-
}
26288
}

spring-data-mongodb/src/test/java/org/springframework/data/mongodb/core/aggregation/OutOperationUnitTest.java

Lines changed: 8 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
1919
import static org.springframework.data.mongodb.test.util.Assertions.*;
2020

21-
import java.util.Arrays;
22-
2321
import org.bson.Document;
2422
import org.junit.jupiter.api.Test;
2523

@@ -30,65 +28,24 @@
3028
* @author Christoph Strobl
3129
* @author Mark Paluch
3230
*/
33-
public class OutOperationUnitTest {
31+
class OutOperationUnitTest {
3432

3533
@Test // DATAMONGO-1418
36-
public void shouldCheckNPEInCreation() {
34+
void shouldCheckNPEInCreation() {
3735
assertThatIllegalArgumentException().isThrownBy(() -> new OutOperation(null));
3836
}
3937

4038
@Test // DATAMONGO-2259
41-
public void shouldUsePreMongoDB42FormatWhenOnlyCollectionIsPresent() {
39+
void shouldUsePreMongoDB42FormatWhenOnlyCollectionIsPresent() {
4240
assertThat(out("out-col").toDocument(Aggregation.DEFAULT_CONTEXT)).isEqualTo(new Document("$out", "out-col"));
4341
}
4442

45-
@Test // DATAMONGO-2259
46-
public void shouldUseMongoDB42ExtendedFormatWhenAdditionalParametersPresent() {
47-
48-
assertThat(out("out-col").insertDocuments().toDocument(Aggregation.DEFAULT_CONTEXT))
49-
.isEqualTo(new Document("$out", new Document("to", "out-col").append("mode", "insertDocuments")));
50-
}
51-
52-
@Test // DATAMONGO-2259
53-
public void shouldRenderExtendedFormatWithJsonStringKey() {
54-
55-
assertThat(out("out-col").insertDocuments() //
56-
.in("database-2") //
57-
.uniqueKey("{ 'field-1' : 1, 'field-2' : 1}") //
58-
.toDocument(Aggregation.DEFAULT_CONTEXT)) //
59-
.containsEntry("$out.to", "out-col") //
60-
.containsEntry("$out.mode", "insertDocuments") //
61-
.containsEntry("$out.db", "database-2") //
62-
.containsEntry("$out.uniqueKey", new Document("field-1", 1).append("field-2", 1));
63-
}
64-
65-
@Test // DATAMONGO-2259
66-
public void shouldRenderExtendedFormatWithSingleFieldKey() {
67-
68-
assertThat(out("out-col").insertDocuments().in("database-2") //
69-
.uniqueKey("field-1").toDocument(Aggregation.DEFAULT_CONTEXT)) //
70-
.containsEntry("$out.to", "out-col") //
71-
.containsEntry("$out.mode", "insertDocuments") //
72-
.containsEntry("$out.db", "database-2") //
73-
.containsEntry("$out.uniqueKey", new Document("field-1", 1));
74-
}
75-
76-
@Test // DATAMONGO-2259
77-
public void shouldRenderExtendedFormatWithMultiFieldKey() {
78-
79-
assertThat(out("out-col").insertDocuments().in("database-2") //
80-
.uniqueKeyOf(Arrays.asList("field-1", "field-2")) //
81-
.toDocument(Aggregation.DEFAULT_CONTEXT)).containsEntry("$out.to", "out-col") //
82-
.containsEntry("$out.mode", "insertDocuments") //
83-
.containsEntry("$out.db", "database-2") //
84-
.containsEntry("$out.uniqueKey", new Document("field-1", 1).append("field-2", 1));
85-
}
86-
87-
@Test // DATAMONGO-2259
88-
public void shouldErrorOnExtendedFormatWithoutMode() {
43+
@Test // DATAMONGO-2259, GH-4969
44+
void shouldRenderDocument() {
8945

90-
assertThatThrownBy(() -> out("out-col").in("database-2").toDocument(Aggregation.DEFAULT_CONTEXT))
91-
.isInstanceOf(IllegalStateException.class);
46+
assertThat(out("out-col").in("database-2").toDocument(Aggregation.DEFAULT_CONTEXT))
47+
.containsEntry("$out.coll", "out-col") //
48+
.containsEntry("$out.db", "database-2");
9249
}
9350

9451
}

0 commit comments

Comments
 (0)