Skip to content

Commit c0be889

Browse files
nhachicharozza
andcommitted
Adding Kotlin extensions methods for projection. (#1515)
* Adding Kotlin extensions methods for projection. Fixes JAVA-5603 --------- Co-authored-by: Ross Lawley <[email protected]>
1 parent ee6d26f commit c0be889

File tree

7 files changed

+623
-8
lines changed

7 files changed

+623
-8
lines changed

config/spotbugs/exclude.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@
229229
<Method name="~.*invoke.*"/>
230230
<Bug pattern="BC_BAD_CAST_TO_ABSTRACT_COLLECTION"/>
231231
</Match>
232+
<Match>
233+
<!-- MongoDB status: "False Positive", SpotBugs rank: 17 -->
234+
<Class name="com.mongodb.kotlin.client.model.Projections"/>
235+
<Method name="~include|exclude"/>
236+
<Bug pattern="BC_BAD_CAST_TO_ABSTRACT_COLLECTION"/>
237+
</Match>
232238

233239
<!-- Spotbugs reports false positives for suspendable operations with default params
234240
see: https://github.com/Kotlin/kotlinx.coroutines/issues/3099

driver-kotlin-extensions/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,11 @@ tasks.withType<Detekt>().configureEach {
118118

119119
spotbugs { showProgress.set(true) }
120120

121+
tasks.spotbugsMain {
122+
// we need the xml report to find out the "rank" (see config/spotbugs/exclude.xml)
123+
reports.getByName("xml") { required.set(true) }
124+
}
125+
121126
// ===========================
122127
// Test Configuration
123128
// ===========================
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
/*
2+
* Copyright 2008-present MongoDB, Inc.
3+
* Copyright (C) 2016/2022 Litote
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
* @custom-license-header
18+
*/
19+
package com.mongodb.kotlin.client.model
20+
21+
import com.mongodb.annotations.Beta
22+
import com.mongodb.annotations.Reason
23+
import com.mongodb.client.model.Aggregates
24+
import com.mongodb.client.model.Projections
25+
import kotlin.reflect.KProperty
26+
import kotlin.reflect.KProperty1
27+
import org.bson.conversions.Bson
28+
29+
/**
30+
* Projection extension methods to improve Kotlin interop
31+
*
32+
* @since 5.3
33+
*/
34+
public object Projections {
35+
36+
/** The projection of the property. This is used in an aggregation pipeline to reference a property from a path. */
37+
public val <T> KProperty<T>.projection: String
38+
get() = path().projection
39+
40+
/** The projection of the property. */
41+
public val String.projection: String
42+
get() = "\$$this"
43+
44+
/** In order to write `$p.p2` */
45+
@JvmSynthetic public infix fun <T0, T1> KProperty1<T0, T1?>.projectionWith(p2: String): String = "$projection.$p2"
46+
47+
/**
48+
* Creates a projection of a property whose value is computed from the given expression. Projection with an
49+
* expression can be used in the following contexts:
50+
* <ul>
51+
* <li>$project aggregation pipeline stage.</li>
52+
* <li>Starting from MongoDB 4.4, it's also accepted in various find-related methods within the {@code
53+
* MongoCollection}-based API where projection is supported, for example: <ul>
54+
* <li>{@code find()}</li>
55+
* <li>{@code findOneAndReplace()}</li>
56+
* <li>{@code findOneAndUpdate()}</li>
57+
* <li>{@code findOneAndDelete()}</li>
58+
* </ul>
59+
*
60+
* </li> </ul>
61+
*
62+
* @param expression the expression
63+
* @param <T> the expression type
64+
* @return the projection
65+
* @see #computedSearchMeta(String)
66+
* @see Aggregates#project(Bson)
67+
*/
68+
@JvmSynthetic
69+
@JvmName("computedFromExt")
70+
public infix fun <T> KProperty<T>.computed(expression: Any): Bson =
71+
Projections.computed(path(), (expression as? KProperty<*>)?.projection ?: expression)
72+
73+
/**
74+
* Creates a projection of a property whose value is computed from the given expression. Projection with an
75+
* expression can be used in the following contexts:
76+
* <ul>
77+
* <li>$project aggregation pipeline stage.</li>
78+
* <li>Starting from MongoDB 4.4, it's also accepted in various find-related methods within the {@code
79+
* MongoCollection}-based API where projection is supported, for example: <ul>
80+
* <li>{@code find()}</li>
81+
* <li>{@code findOneAndReplace()}</li>
82+
* <li>{@code findOneAndUpdate()}</li>
83+
* <li>{@code findOneAndDelete()}</li>
84+
* </ul>
85+
*
86+
* </li> </ul>
87+
*
88+
* @param property the data class property
89+
* @param expression the expression
90+
* @param <T> the expression type
91+
* @return the projection
92+
* @see #computedSearchMeta(String)
93+
* @see Aggregates#project(Bson)
94+
*/
95+
public fun <T> computed(property: KProperty<T>, expression: Any): Bson = property.computed(expression)
96+
97+
/**
98+
* Creates a projection of a String whose value is computed from the given expression. Projection with an expression
99+
* can be used in the following contexts:
100+
* <ul>
101+
* <li>$project aggregation pipeline stage.</li>
102+
* <li>Starting from MongoDB 4.4, it's also accepted in various find-related methods within the {@code
103+
* MongoCollection}-based API where projection is supported, for example: <ul>
104+
* <li>{@code find()}</li>
105+
* <li>{@code findOneAndReplace()}</li>
106+
* <li>{@code findOneAndUpdate()}</li>
107+
* <li>{@code findOneAndDelete()}</li>
108+
* </ul>
109+
*
110+
* </li> </ul>
111+
*
112+
* @param expression the expression
113+
* @return the projection
114+
* @see #computedSearchMeta(String)
115+
* @see Aggregates#project(Bson)
116+
*/
117+
@JvmSynthetic
118+
@JvmName("computedFromExt")
119+
public infix fun String.computed(expression: Any): Bson =
120+
@Suppress("UNCHECKED_CAST")
121+
Projections.computed(this, (expression as? KProperty<Any>)?.projection ?: expression)
122+
123+
/**
124+
* Creates a projection of a String whose value is computed from the given expression. Projection with an expression
125+
* can be used in the following contexts:
126+
* <ul>
127+
* <li>$project aggregation pipeline stage.</li>
128+
* <li>Starting from MongoDB 4.4, it's also accepted in various find-related methods within the {@code
129+
* MongoCollection}-based API where projection is supported, for example: <ul>
130+
* <li>{@code find()}</li>
131+
* <li>{@code findOneAndReplace()}</li>
132+
* <li>{@code findOneAndUpdate()}</li>
133+
* <li>{@code findOneAndDelete()}</li>
134+
* </ul>
135+
*
136+
* </li> </ul>
137+
*
138+
* @param property the data class property
139+
* @param expression the expression
140+
* @return the projection
141+
* @see #computedSearchMeta(String)
142+
* @see Aggregates#project(Bson)
143+
*/
144+
public fun computed(property: String, expression: Any): Bson = property.computed(expression)
145+
146+
/**
147+
* Creates a projection of a property whose value is equal to the {@code $$SEARCH_META} variable. for use with
148+
* {@link Aggregates#search(SearchOperator, SearchOptions)} / {@link Aggregates#search(SearchCollector,
149+
* SearchOptions)}. Calling this method is equivalent to calling {@link #computed(String, Object)} with {@code
150+
* "$$SEARCH_META"} as the second argument.
151+
*
152+
* @param property the data class property
153+
* @return the projection
154+
* @see #computed(String)
155+
* @see Aggregates#project(Bson)
156+
*/
157+
@JvmSynthetic
158+
@JvmName("computedSearchMetaExt")
159+
public fun <T> KProperty<T>.computedSearchMeta(): Bson = Projections.computedSearchMeta(path())
160+
161+
/**
162+
* Creates a projection of a property whose value is equal to the {@code $$SEARCH_META} variable. for use with
163+
* {@link Aggregates#search(SearchOperator, SearchOptions)} / {@link Aggregates#search(SearchCollector,
164+
* SearchOptions)}. Calling this method is equivalent to calling {@link #computed(String, Object)} with {@code
165+
* "$$SEARCH_META"} as the second argument.
166+
*
167+
* @param property the data class property
168+
* @return the projection
169+
* @see #computed(String)
170+
* @see Aggregates#project(Bson)
171+
*/
172+
public fun <T> computedSearchMeta(property: KProperty<T>): Bson = property.computedSearchMeta()
173+
174+
/**
175+
* Creates a projection that includes all of the given properties.
176+
*
177+
* @param properties the field names
178+
* @return the projection
179+
*/
180+
public fun include(vararg properties: KProperty<*>): Bson = include(properties.asList())
181+
182+
/**
183+
* Creates a projection that includes all of the given properties.
184+
*
185+
* @param properties the field names
186+
* @return the projection
187+
*/
188+
public fun include(properties: Iterable<KProperty<*>>): Bson = Projections.include(properties.map { it.path() })
189+
190+
/**
191+
* Creates a projection that excludes all of the given properties.
192+
*
193+
* @param properties the field names
194+
* @return the projection
195+
*/
196+
public fun exclude(vararg properties: KProperty<*>): Bson = exclude(properties.asList())
197+
198+
/**
199+
* Creates a projection that excludes all of the given properties.
200+
*
201+
* @param properties the field names
202+
* @return the projection
203+
*/
204+
public fun exclude(properties: Iterable<KProperty<*>>): Bson = Projections.exclude(properties.map { it.path() })
205+
206+
/**
207+
* Creates a projection that excludes the _id field. This suppresses the automatic inclusion of _id that is the
208+
* default, even when other fields are explicitly included.
209+
*
210+
* @return the projection
211+
*/
212+
public fun excludeId(): Bson = Projections.excludeId()
213+
214+
/**
215+
* Creates a projection that includes for the given property only the first element of an array that matches the
216+
* query filter. This is referred to as the positional $ operator.
217+
*
218+
* @return the projection @mongodb.driver.manual reference/operator/projection/positional/#projection Project the
219+
* first matching element ($ operator)
220+
*/
221+
public val <T> KProperty<T>.elemMatch: Bson
222+
get() = Projections.elemMatch(path())
223+
224+
/**
225+
* Creates a projection that includes for the given property only the first element of the array value of that field
226+
* that matches the given query filter.
227+
*
228+
* @param filter the filter to apply
229+
* @return the projection @mongodb.driver.manual reference/operator/projection/elemMatch elemMatch
230+
*/
231+
@JvmSynthetic
232+
@JvmName("elemMatchProjExt")
233+
public infix fun <T> KProperty<T>.elemMatch(filter: Bson): Bson = Projections.elemMatch(path(), filter)
234+
235+
/**
236+
* Creates a projection that includes for the given property only the first element of the array value of that field
237+
* that matches the given query filter.
238+
*
239+
* @param property the data class property
240+
* @param filter the filter to apply
241+
* @return the projection @mongodb.driver.manual reference/operator/projection/elemMatch elemMatch
242+
*/
243+
public fun <T> elemMatch(property: KProperty<T>, filter: Bson): Bson = property.elemMatch(filter)
244+
245+
/**
246+
* Creates a $meta projection for the given property
247+
*
248+
* @param metaFieldName the meta field name
249+
* @return the projection @mongodb.driver.manual reference/operator/aggregation/meta/
250+
* @see #metaTextScore(String)
251+
* @see #metaSearchScore(String)
252+
* @see #metaVectorSearchScore(String)
253+
* @see #metaSearchHighlights(String)
254+
*/
255+
@JvmSynthetic
256+
@JvmName("metaExt")
257+
public infix fun <T> KProperty<T>.meta(metaFieldName: String): Bson = Projections.meta(path(), metaFieldName)
258+
259+
/**
260+
* Creates a $meta projection for the given property
261+
*
262+
* @param property the data class property
263+
* @param metaFieldName the meta field name
264+
* @return the projection @mongodb.driver.manual reference/operator/aggregation/meta/
265+
* @see #metaTextScore(String)
266+
* @see #metaSearchScore(String)
267+
* @see #metaVectorSearchScore(String)
268+
* @see #metaSearchHighlights(String)
269+
*/
270+
public fun <T> meta(property: KProperty<T>, metaFieldName: String): Bson = property.meta(metaFieldName)
271+
272+
/**
273+
* Creates a textScore projection for the given property, for use with text queries. Calling this method is
274+
* equivalent to calling {@link #meta(String)} with {@code "textScore"} as the argument.
275+
*
276+
* @return the projection
277+
* @see Filters#text(String, TextSearchOptions) @mongodb.driver.manual
278+
* reference/operator/aggregation/meta/#text-score-metadata--meta---textscore- textScore
279+
*/
280+
public fun <T> KProperty<T>.metaTextScore(): Bson = Projections.metaTextScore(path())
281+
282+
/**
283+
* Creates a searchScore projection for the given property, for use with {@link Aggregates#search(SearchOperator,
284+
* SearchOptions)} / {@link Aggregates#search(SearchCollector, SearchOptions)}. Calling this method is equivalent to
285+
* calling {@link #meta(String, String)} with {@code "searchScore"} as the argument.
286+
*
287+
* @return the projection @mongodb.atlas.manual atlas-search/scoring/ Scoring
288+
*/
289+
public fun <T> KProperty<T>.metaSearchScore(): Bson = Projections.metaSearchScore(path())
290+
291+
/**
292+
* Creates a vectorSearchScore projection for the given property, for use with {@link
293+
* Aggregates#vectorSearch(FieldSearchPath, Iterable, String, long, VectorSearchOptions)} . Calling this method is
294+
* equivalent to calling {@link #meta(String, String)} with {@code "vectorSearchScore"} as the argument.
295+
*
296+
* @return the projection @mongodb.atlas.manual atlas-search/scoring/ Scoring @mongodb.server.release 6.0.10
297+
*/
298+
@Beta(Reason.SERVER)
299+
public fun <T> KProperty<T>.metaVectorSearchScore(): Bson = Projections.metaVectorSearchScore(path())
300+
301+
/**
302+
* Creates a searchHighlights projection for the given property, for use with {@link
303+
* Aggregates#search(SearchOperator, SearchOptions)} / {@link Aggregates#search(SearchCollector, SearchOptions)}.
304+
* Calling this method is equivalent to calling {@link #meta(String, String)} with {@code "searchHighlights"} as the
305+
* argument.
306+
*
307+
* @return the projection
308+
* @see com.mongodb.client.model.search.SearchHighlight @mongodb.atlas.manual atlas-search/highlighting/
309+
* Highlighting
310+
*/
311+
public fun <T> KProperty<T>.metaSearchHighlights(): Bson = Projections.metaSearchHighlights(path())
312+
313+
/**
314+
* Creates a projection to the given property of a slice of the array value of that field.
315+
*
316+
* @param limit the number of elements to project.
317+
* @return the projection @mongodb.driver.manual reference/operator/projection/slice Slice
318+
*/
319+
public infix fun <T> KProperty<T>.slice(limit: Int): Bson = Projections.slice(path(), limit)
320+
321+
/**
322+
* Creates a projection to the given property of a slice of the array value of that field.
323+
*
324+
* @param skip the number of elements to skip before applying the limit
325+
* @param limit the number of elements to project
326+
* @return the projection @mongodb.driver.manual reference/operator/projection/slice Slice
327+
*/
328+
public fun <T> KProperty<T>.slice(skip: Int, limit: Int): Bson = Projections.slice(path(), skip, limit)
329+
330+
/**
331+
* Creates a projection that combines the list of projections into a single one. If there are duplicate keys, the
332+
* last one takes precedence.
333+
*
334+
* @param projections the list of projections to combine
335+
* @return the combined projection
336+
*/
337+
public fun fields(vararg projections: Bson): Bson = Projections.fields(*projections)
338+
339+
/**
340+
* Creates a projection that combines the list of projections into a single one. If there are duplicate keys, the
341+
* last one takes precedence.
342+
*
343+
* @param projections the list of projections to combine
344+
* @return the combined projection @mongodb.driver.manual
345+
*/
346+
public fun fields(projections: List<Bson>): Bson = Projections.fields(projections)
347+
}

0 commit comments

Comments
 (0)