Skip to content

Commit fc5e59b

Browse files
committed
DATAMONGO-2138 - Typed Query Extensions
1 parent 58c6cf9 commit fc5e59b

File tree

4 files changed

+389
-0
lines changed

4 files changed

+389
-0
lines changed
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
/*
2+
* Copyright 2010-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.mongodb.core
18+
19+
import com.mongodb.client.result.DeleteResult
20+
import com.mongodb.client.result.UpdateResult
21+
import org.springframework.data.mongodb.core.mapreduce.MapReduceOptions
22+
import org.springframework.data.mongodb.core.mapreduce.MapReduceResults
23+
import org.springframework.data.mongodb.core.query.TypedQuery
24+
import org.springframework.data.mongodb.core.query.Update
25+
import org.springframework.data.util.CloseableIterator
26+
import kotlin.reflect.KClass
27+
28+
/**
29+
* Extension for [MongoOperations.stream] leveraging reified type parameters.
30+
*
31+
* @author Tjeu Kayim
32+
*/
33+
inline fun <reified T : Any> MongoOperations.stream(typedQuery: TypedQuery<T>): CloseableIterator<T> =
34+
stream(typedQuery, T::class.java)
35+
36+
/**
37+
* Extension for [MongoOperations.stream] leveraging reified type parameters.
38+
*
39+
* @author Tjeu Kayim
40+
*/
41+
inline fun <reified T : Any> MongoOperations.stream(typedQuery: TypedQuery<T>, collectionName: String? = null): CloseableIterator<T> =
42+
if (collectionName != null) stream(typedQuery, T::class.java, collectionName)
43+
else stream(typedQuery, T::class.java)
44+
45+
/**
46+
* Extension for [MongoOperations.mapReduce] leveraging reified type parameters.
47+
*
48+
* @author Tjeu Kayim
49+
*/
50+
inline fun <reified T : Any> MongoOperations.mapReduce(typedQuery: TypedQuery<T>, collectionName: String, mapFunction: String, reduceFunction: String, options: MapReduceOptions? = null): MapReduceResults<T> =
51+
if (options != null) mapReduce(typedQuery, collectionName, mapFunction, reduceFunction, options, T::class.java)
52+
else mapReduce(typedQuery, collectionName, mapFunction, reduceFunction, T::class.java)
53+
54+
/**
55+
* Extension for [MongoOperations.findOne] leveraging reified type parameters.
56+
*
57+
* @author Tjeu Kayim
58+
*/
59+
inline fun <reified T : Any> MongoOperations.findOne(typedQuery: TypedQuery<T>, collectionName: String? = null): T? =
60+
if (collectionName != null) findOne(typedQuery, T::class.java, collectionName) else findOne(typedQuery, T::class.java)
61+
62+
/**
63+
* Extension for [MongoOperations.exists] providing a [KClass] based variant.
64+
*
65+
* @author Tjeu Kayim
66+
*/
67+
fun <T : Any> MongoOperations.exists(typedQuery: TypedQuery<T>, entityClass: KClass<T>, collectionName: String? = null): Boolean =
68+
if (collectionName != null) exists(typedQuery, entityClass.java, collectionName)
69+
else exists(typedQuery, entityClass.java)
70+
71+
/**
72+
* Extension for [MongoOperations.exists] leveraging reified type parameters.
73+
*
74+
* @author Tjeu Kayim
75+
*/
76+
inline fun <reified T : Any> MongoOperations.exists(typedQuery: TypedQuery<T>, collectionName: String? = null): Boolean =
77+
if (collectionName != null) exists(typedQuery, T::class.java, collectionName)
78+
else exists(typedQuery, T::class.java)
79+
80+
/**
81+
* Extension for [MongoOperations.find] leveraging reified type parameters.
82+
*
83+
* @author Tjeu Kayim
84+
*/
85+
inline fun <reified T : Any> MongoOperations.find(typedQuery: TypedQuery<T>, collectionName: String? = null): List<T> =
86+
if (collectionName != null) find(typedQuery, T::class.java, collectionName)
87+
else find(typedQuery, T::class.java)
88+
89+
/**
90+
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
91+
*
92+
* @author Christoph Strobl
93+
* @since 2.1
94+
*/
95+
inline fun <reified T : Any> MongoOperations.findDistinct(typedQuery: TypedQuery<T>, field: String, entityClass: KClass<*>): List<T> =
96+
findDistinct(typedQuery, field, entityClass.java, T::class.java)
97+
98+
/**
99+
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
100+
*
101+
* @author Christoph Strobl
102+
* @since 2.1
103+
*/
104+
inline fun <reified T : Any> MongoOperations.findDistinct(typedQuery: TypedQuery<T>, field: String, collectionName: String, entityClass: KClass<*>): List<T> =
105+
findDistinct(typedQuery, field, collectionName, entityClass.java, T::class.java)
106+
107+
/**
108+
* Extension for [MongoOperations.findDistinct] leveraging reified type parameters.
109+
*
110+
* @author Christoph Strobl
111+
* @author Mark Paluch
112+
* @since 2.1
113+
*/
114+
inline fun <reified T : Any, reified E : Any> MongoOperations.findDistinct(typedQuery: TypedQuery<T>, field: String, collectionName: String? = null): List<T> =
115+
if (collectionName != null) findDistinct(typedQuery, field, collectionName, E::class.java, T::class.java)
116+
else findDistinct(typedQuery, field, E::class.java, T::class.java)
117+
118+
/**
119+
* Extension for [MongoOperations.findAndModify] leveraging reified type parameters.
120+
*
121+
* @author Tjeu Kayim
122+
*/
123+
inline fun <reified T : Any> MongoOperations.findAndModify(typedQuery: TypedQuery<T>, update: Update, options: FindAndModifyOptions, collectionName: String? = null): T? =
124+
if (collectionName != null) findAndModify(typedQuery, update, options, T::class.java, collectionName)
125+
else findAndModify(typedQuery, update, options, T::class.java)
126+
127+
/**
128+
* Extension for [MongoOperations.findAndRemove] leveraging reified type parameters.
129+
*
130+
* @author Tjeu Kayim
131+
*/
132+
inline fun <reified T : Any> MongoOperations.findAndRemove(typedQuery: TypedQuery<T>, collectionName: String? = null): T? =
133+
if (collectionName != null) findAndRemove(typedQuery, T::class.java, collectionName)
134+
else findAndRemove(typedQuery, T::class.java)
135+
136+
/**
137+
* Extension for [MongoOperations.count] providing a [KClass] based variant.
138+
*
139+
* @author Tjeu Kayim
140+
*/
141+
fun <T : Any> MongoOperations.count(typedQuery: TypedQuery<T> = TypedQuery(), entityClass: KClass<T>, collectionName: String? = null): Long =
142+
if (collectionName != null) count(typedQuery, entityClass.java, collectionName)
143+
else count(typedQuery, entityClass.java)
144+
145+
/**
146+
* Extension for [MongoOperations.count] leveraging reified type parameters.
147+
*
148+
* @author Tjeu Kayim
149+
*/
150+
inline fun <reified T : Any> MongoOperations.count(typedQuery: TypedQuery<T> = TypedQuery(), collectionName: String? = null): Long =
151+
if (collectionName != null) count(typedQuery, T::class.java, collectionName) else count(typedQuery, T::class.java)
152+
153+
/**
154+
* Extension for [MongoOperations.upsert] providing a [KClass] based variant.
155+
*
156+
* @author Tjeu Kayim
157+
*/
158+
fun <T : Any> MongoOperations.upsert(typedQuery: TypedQuery<T>, update: Update, entityClass: KClass<T>, collectionName: String? = null): UpdateResult =
159+
if (collectionName != null) upsert(typedQuery, update, entityClass.java, collectionName)
160+
else upsert(typedQuery, update, entityClass.java)
161+
162+
/**
163+
* Extension for [MongoOperations.upsert] leveraging reified type parameters.
164+
*
165+
* @author Tjeu Kayim
166+
*/
167+
inline fun <reified T : Any> MongoOperations.upsert(typedQuery: TypedQuery<T>, update: Update, collectionName: String? = null): UpdateResult =
168+
if (collectionName != null) upsert(typedQuery, update, T::class.java, collectionName)
169+
else upsert(typedQuery, update, T::class.java)
170+
171+
/**
172+
* Extension for [MongoOperations.updateFirst] providing a [KClass] based variant.
173+
*
174+
* @author Tjeu Kayim
175+
*/
176+
fun <T : Any> MongoOperations.updateFirst(typedQuery: TypedQuery<T>, update: Update, entityClass: KClass<T>, collectionName: String? = null): UpdateResult =
177+
if (collectionName != null) updateFirst(typedQuery, update, entityClass.java, collectionName)
178+
else updateFirst(typedQuery, update, entityClass.java)
179+
180+
/**
181+
* Extension for [MongoOperations.updateFirst] leveraging reified type parameters.
182+
*
183+
* @author Tjeu Kayim
184+
*/
185+
inline fun <reified T : Any> MongoOperations.updateFirst(typedQuery: TypedQuery<T>, update: Update, collectionName: String? = null): UpdateResult =
186+
if (collectionName != null) updateFirst(typedQuery, update, T::class.java, collectionName)
187+
else updateFirst(typedQuery, update, T::class.java)
188+
189+
/**
190+
* Extension for [MongoOperations.updateMulti] providing a [KClass] based variant.
191+
*
192+
* @author Tjeu Kayim
193+
*/
194+
fun <T : Any> MongoOperations.updateMulti(typedQuery: TypedQuery<T>, update: Update, entityClass: KClass<T>, collectionName: String? = null): UpdateResult =
195+
if (collectionName != null) updateMulti(typedQuery, update, entityClass.java, collectionName)
196+
else updateMulti(typedQuery, update, entityClass.java)
197+
198+
/**
199+
* Extension for [MongoOperations.updateMulti] leveraging reified type parameters.
200+
*
201+
* @author Tjeu Kayim
202+
*/
203+
inline fun <reified T : Any> MongoOperations.updateMulti(typedQuery: TypedQuery<T>, update: Update, collectionName: String? = null): UpdateResult =
204+
if (collectionName != null) updateMulti(typedQuery, update, T::class.java, collectionName)
205+
else updateMulti(typedQuery, update, T::class.java)
206+
207+
/**
208+
* Extension for [MongoOperations.remove] providing a [KClass] based variant.
209+
*
210+
* @author Tjeu Kayim
211+
*/
212+
fun <T : Any> MongoOperations.remove(typedQuery: TypedQuery<T>, entityClass: KClass<T>, collectionName: String? = null): DeleteResult =
213+
if (collectionName != null) remove(typedQuery, entityClass.java, collectionName)
214+
else remove(typedQuery, entityClass.java)
215+
216+
/**
217+
* Extension for [MongoOperations.remove] leveraging reified type parameters.
218+
*
219+
* @author Tjeu Kayim
220+
*/
221+
inline fun <reified T : Any> MongoOperations.remove(typedQuery: TypedQuery<T>, collectionName: String? = null): DeleteResult =
222+
if (collectionName != null) remove(typedQuery, T::class.java, collectionName)
223+
else remove(typedQuery, T::class.java)
224+
225+
/**
226+
* Extension for [MongoOperations.findAllAndRemove] leveraging reified type parameters.
227+
*
228+
* @author Tjeu Kayim
229+
*/
230+
inline fun <reified T : Any> MongoOperations.findAllAndRemove(typedQuery: TypedQuery<T>): List<T> =
231+
findAllAndRemove(typedQuery, T::class.java)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2010-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.query
17+
18+
import org.bson.Document
19+
import kotlin.reflect.KProperty1
20+
21+
/**
22+
* @author Tjeu Kayim
23+
*/
24+
class TypedCriteria<T> : CriteriaDefinition<T> {
25+
override fun getCriteriaObject(): Document = chain.criteriaObject
26+
27+
override fun getKey(): String? = chain.key
28+
29+
private var chain = Criteria()
30+
31+
infix fun <U : Any> KProperty1<T, U>.gt(value: U) {
32+
chain = chain.and(name).gt(value)
33+
}
34+
35+
infix fun <U : Any> KProperty1<T, U>.isEqualTo(value: U) {
36+
chain = chain.and(name).isEqualTo(value)
37+
}
38+
}
39+
40+
fun <T> typedCriteria(block: TypedCriteria<T>.() -> Unit): CriteriaDefinition<T> {
41+
val typedCriteria = TypedCriteria<T>()
42+
typedCriteria.block()
43+
return typedCriteria
44+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2010-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.mongodb.core.query
17+
18+
/**
19+
* Type safe query builder.
20+
*
21+
* @author Tjeu Kayim
22+
*/
23+
class TypedQuery<T : Any> : Query {
24+
constructor(criteria: CriteriaDefinition<T>) : super(criteria)
25+
constructor() : super()
26+
27+
}
28+
29+
fun <T : Any> typedQuery(block: TypedCriteria<T>.() -> Unit): TypedQuery<T> {
30+
return TypedQuery(typedCriteria(block))
31+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2010-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.data.mongodb.core.query
18+
19+
import com.nhaarman.mockito_kotlin.mock
20+
import org.bson.types.ObjectId
21+
import org.junit.Assert.assertEquals
22+
import org.junit.Test
23+
import org.springframework.data.mongodb.core.MongoOperations
24+
import org.springframework.data.mongodb.core.find
25+
import org.springframework.data.mongodb.core.mapping.Document
26+
27+
/**
28+
* @author Tjeu Kayim
29+
*/
30+
class TypedQueryTest {
31+
32+
val operations: MongoOperations = mock()
33+
34+
@Test
35+
fun `Typed query gt and isEqualTo`() {
36+
val classic = Criteria("price").gt(1100)
37+
.and("available").isEqualTo(true)
38+
39+
val typed = typedCriteria<Book> {
40+
Book::price gt 1100
41+
Book::available isEqualTo true
42+
}
43+
44+
assertEquals(classic.criteriaObject, typed.criteriaObject)
45+
}
46+
47+
@Test
48+
fun `MongoOperations find by typed query`() {
49+
operations.find<Book>(typedQuery { Book::price gt 1100 })
50+
}
51+
52+
@Test
53+
fun `Test TypedQuery`() {
54+
val classic = Query(
55+
Criteria()
56+
.and("price").gt(1100)
57+
.and("available").isEqualTo(true)
58+
)
59+
60+
val typed = typedQuery<Book> {
61+
Book::price gt 1100
62+
Book::available isEqualTo true
63+
}
64+
65+
assertEquals(classic.queryObject, typed.queryObject)
66+
}
67+
}
68+
69+
@Document("books")
70+
data class Book(
71+
val id: ObjectId,
72+
val name: String,
73+
val price: Int,
74+
val available: Boolean
75+
)
76+
77+
@Document("books")
78+
data class AnotherBook(
79+
val id: ObjectId,
80+
val name: String,
81+
val price: Int,
82+
val available: Boolean
83+
)

0 commit comments

Comments
 (0)