DATAMONGO-2138 - Polishing.

Rename NestedProperty to KPropertyPath to reflect the underlying concept in alignment with our own PropertyPath type. Rename nestedFieldName(…) method to asString(…) to align with Kotlin method terminology. Reformat.

Slightly reword documentation. Add Type-safe Queries for Kotlin to What's New section.

Original pull request: #622.
This commit is contained in:
Mark Paluch
2018-12-10 15:47:22 +01:00
parent 8d969ef41d
commit 2ca879f534
7 changed files with 256 additions and 205 deletions

View File

@@ -23,7 +23,7 @@ import kotlin.reflect.KProperty
* @author Sebastien Deleuze
* @since 2.0
*/
fun Criteria.isEqualTo(o: Any?) : Criteria = `is`(o)
fun Criteria.isEqualTo(o: Any?): Criteria = `is`(o)
/**
* Extension for [Criteria.in] providing an `inValues` alias since `in` is a reserved keyword in Kotlin.
@@ -31,7 +31,7 @@ fun Criteria.isEqualTo(o: Any?) : Criteria = `is`(o)
* @author Sebastien Deleuze
* @since 2.0
*/
fun <T: Any?> Criteria.inValues(c: Collection<T>) : Criteria = `in`(c)
fun <T : Any?> Criteria.inValues(c: Collection<T>): Criteria = `in`(c)
/**
* Extension for [Criteria.in] providing an `inValues` alias since `in` is a reserved keyword in Kotlin.
@@ -39,19 +39,20 @@ fun <T: Any?> Criteria.inValues(c: Collection<T>) : Criteria = `in`(c)
* @author Sebastien Deleuze
* @since 2.0
*/
fun Criteria.inValues(vararg o: Any?) : Criteria = `in`(*o)
fun Criteria.inValues(vararg o: Any?): Criteria = `in`(*o)
/**
* Creates a Criteria using a KProperty as key.
* Supports nested field names with [NestedProperty].
* Supports nested field names with [KPropertyPath].
* @author Tjeu Kayim
* @since 2.2
*/
fun where(key: KProperty<*>): Criteria = Criteria.where(nestedFieldName(key))
fun where(key: KProperty<*>): Criteria = Criteria.where(asString(key))
/**
* Add new key to the criteria chain using a KProperty.
* Supports nested field names with [NestedProperty].
* Supports nested field names with [KPropertyPath].
* @author Tjeu Kayim
* @since 2.2
*/
infix fun Criteria.and(key: KProperty<*>): Criteria = and(nestedFieldName(key))
infix fun Criteria.and(key: KProperty<*>): Criteria = and(asString(key))

View File

@@ -19,29 +19,30 @@ import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1
/**
* Refer to a field in an embedded/nested document.
* Abstraction of a property path consisting of [KProperty].
* @author Tjeu Kayim
* @author Mark Paluch
* @since 2.2
*/
class NestedProperty<T, U>(
internal val parent: KProperty<U>,
internal val child: KProperty1<U, T>
class KPropertyPath<T, U>(
internal val parent: KProperty<U>,
internal val child: KProperty1<U, T>
) : KProperty<T> by child
/**
* Recursively construct field name for a nested property.
* @author Tjeu Kayim
*/
internal fun nestedFieldName(property: KProperty<*>): String {
internal fun asString(property: KProperty<*>): String {
return when (property) {
is NestedProperty<*, *> ->
"${nestedFieldName(property.parent)}.${property.child.name}"
is KPropertyPath<*, *> ->
"${asString(property.parent)}.${property.child.name}"
else -> property.name
}
}
/**
* Builds [NestedProperty] from Property References.
* Builds [KPropertyPath] from Property References.
* Refer to a field in an embedded/nested document.
*
* For example, referring to the field "book.author":
@@ -52,4 +53,4 @@ internal fun nestedFieldName(property: KProperty<*>): String {
* @since 2.2
*/
operator fun <T, U> KProperty<T>.div(other: KProperty1<T, U>) =
NestedProperty(this, other)
KPropertyPath(this, other)

View File

@@ -31,7 +31,7 @@ import kotlin.reflect.KProperty
* @see Criteria.isEqualTo
*/
infix fun <T> KProperty<T>.isEqualTo(value: T) =
Criteria(nestedFieldName(this)).isEqualTo(value)
Criteria(asString(this)).isEqualTo(value)
/**
* Creates a criterion using the $ne operator.
@@ -42,7 +42,7 @@ infix fun <T> KProperty<T>.isEqualTo(value: T) =
* @see Criteria.ne
*/
infix fun <T> KProperty<T>.ne(value: T): Criteria =
Criteria(nestedFieldName(this)).ne(value)
Criteria(asString(this)).ne(value)
/**
* Creates a criterion using the $lt operator.
@@ -53,7 +53,7 @@ infix fun <T> KProperty<T>.ne(value: T): Criteria =
* @see Criteria.lt
*/
infix fun <T> KProperty<T>.lt(value: T): Criteria =
Criteria(nestedFieldName(this)).lt(value)
Criteria(asString(this)).lt(value)
/**
* Creates a criterion using the $lte operator.
@@ -64,7 +64,7 @@ infix fun <T> KProperty<T>.lt(value: T): Criteria =
* @see Criteria.lte
*/
infix fun <T> KProperty<T>.lte(value: T): Criteria =
Criteria(nestedFieldName(this)).lte(value)
Criteria(asString(this)).lte(value)
/**
* Creates a criterion using the $gt operator.
@@ -75,7 +75,7 @@ infix fun <T> KProperty<T>.lte(value: T): Criteria =
* @see Criteria.gt
*/
infix fun <T> KProperty<T>.gt(value: T): Criteria =
Criteria(nestedFieldName(this)).gt(value)
Criteria(asString(this)).gt(value)
/**
* Creates a criterion using the $gte operator.
@@ -86,7 +86,7 @@ infix fun <T> KProperty<T>.gt(value: T): Criteria =
* @see Criteria.gte
*/
infix fun <T> KProperty<T>.gte(value: T): Criteria =
Criteria(nestedFieldName(this)).gte(value)
Criteria(asString(this)).gte(value)
/**
* Creates a criterion using the $in operator.
@@ -97,7 +97,7 @@ infix fun <T> KProperty<T>.gte(value: T): Criteria =
* @see Criteria.inValues
*/
fun <T> KProperty<T>.inValues(vararg o: Any): Criteria =
Criteria(nestedFieldName(this)).`in`(*o)
Criteria(asString(this)).`in`(*o)
/**
* Creates a criterion using the $in operator.
@@ -108,7 +108,7 @@ fun <T> KProperty<T>.inValues(vararg o: Any): Criteria =
* @see Criteria.inValues
*/
infix fun <T> KProperty<T>.inValues(value: Collection<T>): Criteria =
Criteria(nestedFieldName(this)).`in`(value)
Criteria(asString(this)).`in`(value)
/**
* Creates a criterion using the $nin operator.
@@ -119,7 +119,7 @@ infix fun <T> KProperty<T>.inValues(value: Collection<T>): Criteria =
* @see Criteria.nin
*/
fun <T> KProperty<T>.nin(vararg o: Any): Criteria =
Criteria(nestedFieldName(this)).nin(*o)
Criteria(asString(this)).nin(*o)
/**
* Creates a criterion using the $nin operator.
@@ -130,7 +130,7 @@ fun <T> KProperty<T>.nin(vararg o: Any): Criteria =
* @see Criteria.nin
*/
infix fun <T> KProperty<T>.nin(value: Collection<T>): Criteria =
Criteria(nestedFieldName(this)).nin(value)
Criteria(asString(this)).nin(value)
/**
* Creates a criterion using the $mod operator.
@@ -141,7 +141,7 @@ infix fun <T> KProperty<T>.nin(value: Collection<T>): Criteria =
* @see Criteria.mod
*/
fun KProperty<Number>.mod(value: Number, remainder: Number): Criteria =
Criteria(nestedFieldName(this)).mod(value, remainder)
Criteria(asString(this)).mod(value, remainder)
/**
* Creates a criterion using the $all operator.
@@ -152,7 +152,7 @@ fun KProperty<Number>.mod(value: Number, remainder: Number): Criteria =
* @see Criteria.all
*/
fun KProperty<*>.all(vararg o: Any): Criteria =
Criteria(nestedFieldName(this)).all(*o)
Criteria(asString(this)).all(*o)
/**
* Creates a criterion using the $all operator.
@@ -163,7 +163,7 @@ fun KProperty<*>.all(vararg o: Any): Criteria =
* @see Criteria.all
*/
infix fun KProperty<*>.all(value: Collection<*>): Criteria =
Criteria(nestedFieldName(this)).all(value)
Criteria(asString(this)).all(value)
/**
* Creates a criterion using the $size operator.
@@ -174,7 +174,7 @@ infix fun KProperty<*>.all(value: Collection<*>): Criteria =
* @see Criteria.size
*/
infix fun KProperty<*>.size(s: Int): Criteria =
Criteria(nestedFieldName(this)).size(s)
Criteria(asString(this)).size(s)
/**
* Creates a criterion using the $exists operator.
@@ -185,7 +185,7 @@ infix fun KProperty<*>.size(s: Int): Criteria =
* @see Criteria.exists
*/
infix fun KProperty<*>.exists(b: Boolean): Criteria =
Criteria(nestedFieldName(this)).exists(b)
Criteria(asString(this)).exists(b)
/**
* Creates a criterion using the $type operator.
@@ -196,7 +196,7 @@ infix fun KProperty<*>.exists(b: Boolean): Criteria =
* @see Criteria.type
*/
infix fun KProperty<*>.type(t: Int): Criteria =
Criteria(nestedFieldName(this)).type(t)
Criteria(asString(this)).type(t)
/**
* Creates a criterion using the $type operator.
@@ -207,7 +207,7 @@ infix fun KProperty<*>.type(t: Int): Criteria =
* @see Criteria.type
*/
infix fun KProperty<*>.type(t: Collection<JsonSchemaObject.Type>): Criteria =
Criteria(nestedFieldName(this)).type(*t.toTypedArray())
Criteria(asString(this)).type(*t.toTypedArray())
/**
* Creates a criterion using the $type operator.
@@ -218,7 +218,7 @@ infix fun KProperty<*>.type(t: Collection<JsonSchemaObject.Type>): Criteria =
* @see Criteria.type
*/
fun KProperty<*>.type(vararg t: JsonSchemaObject.Type): Criteria =
Criteria(nestedFieldName(this)).type(*t)
Criteria(asString(this)).type(*t)
/**
* Creates a criterion using the $not meta operator which affects the clause directly following
@@ -229,7 +229,7 @@ fun KProperty<*>.type(vararg t: JsonSchemaObject.Type): Criteria =
* @see Criteria.not
*/
fun KProperty<*>.not(): Criteria =
Criteria(nestedFieldName(this)).not()
Criteria(asString(this)).not()
/**
* Creates a criterion using a $regex operator.
@@ -240,7 +240,7 @@ fun KProperty<*>.not(): Criteria =
* @see Criteria.regex
*/
infix fun KProperty<String?>.regex(re: String): Criteria =
Criteria(nestedFieldName(this)).regex(re, null)
Criteria(asString(this)).regex(re, null)
/**
* Creates a criterion using a $regex and $options operator.
@@ -251,7 +251,7 @@ infix fun KProperty<String?>.regex(re: String): Criteria =
* @see Criteria.regex
*/
fun KProperty<String?>.regex(re: String, options: String?): Criteria =
Criteria(nestedFieldName(this)).regex(re, options)
Criteria(asString(this)).regex(re, options)
/**
* Syntactical sugar for [isEqualTo] making obvious that we create a regex predicate.
@@ -260,7 +260,7 @@ fun KProperty<String?>.regex(re: String, options: String?): Criteria =
* @see Criteria.regex
*/
infix fun KProperty<String?>.regex(re: Regex): Criteria =
Criteria(nestedFieldName(this)).regex(re.toPattern())
Criteria(asString(this)).regex(re.toPattern())
/**
* Syntactical sugar for [isEqualTo] making obvious that we create a regex predicate.
@@ -269,7 +269,7 @@ infix fun KProperty<String?>.regex(re: Regex): Criteria =
* @see Criteria.regex
*/
infix fun KProperty<String?>.regex(re: Pattern): Criteria =
Criteria(nestedFieldName(this)).regex(re)
Criteria(asString(this)).regex(re)
/**
* Syntactical sugar for [isEqualTo] making obvious that we create a regex predicate.
@@ -278,35 +278,35 @@ infix fun KProperty<String?>.regex(re: Pattern): Criteria =
* @see Criteria.regex
*/
infix fun KProperty<String?>.regex(re: BsonRegularExpression): Criteria =
Criteria(nestedFieldName(this)).regex(re)
Criteria(asString(this)).regex(re)
/**
* Creates a geospatial criterion using a $geoWithin $centerSphere operation. This is only available for
* Mongo 2.4 and higher.
*
* See [MongoDB Query operator:
* $geoWithin](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/)
* $geoWithin](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/)
*
* See [MongoDB Query operator:
* $centerSphere](https://docs.mongodb.com/manual/reference/operator/query/centerSphere/)
* $centerSphere](https://docs.mongodb.com/manual/reference/operator/query/centerSphere/)
* @author Tjeu Kayim
* @since 2.2
* @see Criteria.withinSphere
*/
infix fun KProperty<GeoJson<*>>.withinSphere(circle: Circle): Criteria =
Criteria(nestedFieldName(this)).withinSphere(circle)
Criteria(asString(this)).withinSphere(circle)
/**
* Creates a geospatial criterion using a $geoWithin operation.
*
* See [MongoDB Query operator:
* $geoWithin](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/)
* $geoWithin](https://docs.mongodb.com/manual/reference/operator/query/geoWithin/)
* @author Tjeu Kayim
* @since 2.2
* @see Criteria.within
*/
infix fun KProperty<GeoJson<*>>.within(shape: Shape): Criteria =
Criteria(nestedFieldName(this)).within(shape)
Criteria(asString(this)).within(shape)
/**
* Creates a geospatial criterion using a $near operation.
@@ -317,20 +317,20 @@ infix fun KProperty<GeoJson<*>>.within(shape: Shape): Criteria =
* @see Criteria.near
*/
infix fun KProperty<GeoJson<*>>.near(point: Point): Criteria =
Criteria(nestedFieldName(this)).near(point)
Criteria(asString(this)).near(point)
/**
* Creates a geospatial criterion using a $nearSphere operation. This is only available for Mongo 1.7 and
* higher.
*
* See [MongoDB Query operator:
* $nearSphere](https://docs.mongodb.com/manual/reference/operator/query/nearSphere/)
* $nearSphere](https://docs.mongodb.com/manual/reference/operator/query/nearSphere/)
* @author Tjeu Kayim
* @since 2.2
* @see Criteria.nearSphere
*/
infix fun KProperty<GeoJson<*>>.nearSphere(point: Point): Criteria =
Criteria(nestedFieldName(this)).nearSphere(point)
Criteria(asString(this)).nearSphere(point)
/**
* Creates criterion using `$geoIntersects` operator which matches intersections of the given `geoJson`
@@ -340,19 +340,19 @@ infix fun KProperty<GeoJson<*>>.nearSphere(point: Point): Criteria =
* @see Criteria.intersects
*/
infix fun KProperty<GeoJson<*>>.intersects(geoJson: GeoJson<*>): Criteria =
Criteria(nestedFieldName(this)).intersects(geoJson)
Criteria(asString(this)).intersects(geoJson)
/**
* Creates a geo-spatial criterion using a $maxDistance operation, for use with $near
*
* See [MongoDB Query operator:
* $maxDistance](https://docs.mongodb.com/manual/reference/operator/query/maxDistance/)
* $maxDistance](https://docs.mongodb.com/manual/reference/operator/query/maxDistance/)
* @author Tjeu Kayim
* @since 2.2
* @see Criteria.maxDistance
*/
infix fun KProperty<GeoJson<*>>.maxDistance(d: Double): Criteria =
Criteria(nestedFieldName(this)).maxDistance(d)
Criteria(asString(this)).maxDistance(d)
/**
* Creates a geospatial criterion using a $minDistance operation, for use with $near or
@@ -362,19 +362,19 @@ infix fun KProperty<GeoJson<*>>.maxDistance(d: Double): Criteria =
* @see Criteria.minDistance
*/
infix fun KProperty<GeoJson<*>>.minDistance(d: Double): Criteria =
Criteria(nestedFieldName(this)).minDistance(d)
Criteria(asString(this)).minDistance(d)
/**
* Creates a criterion using the $elemMatch operator
*
* See [MongoDB Query operator:
* $elemMatch](https://docs.mongodb.com/manual/reference/operator/query/elemMatch/)
* $elemMatch](https://docs.mongodb.com/manual/reference/operator/query/elemMatch/)
* @author Tjeu Kayim
* @since 2.2
* @see Criteria.elemMatch
*/
infix fun KProperty<*>.elemMatch(c: Criteria): Criteria =
Criteria(nestedFieldName(this)).elemMatch(c)
Criteria(asString(this)).elemMatch(c)
/**
* Use [Criteria.BitwiseCriteriaOperators] as gateway to create a criterion using one of the
@@ -390,4 +390,4 @@ infix fun KProperty<*>.elemMatch(c: Criteria): Criteria =
* @see Criteria.bits
*/
infix fun KProperty<*>.bits(bitwiseCriteria: Criteria.BitwiseCriteriaOperators.() -> Criteria) =
Criteria(nestedFieldName(this)).bits().let(bitwiseCriteria)
Criteria(asString(this)).bits().let(bitwiseCriteria)

View File

@@ -21,12 +21,12 @@ import org.junit.Test
/**
* @author Tjeu Kayim
*/
class NestedPropertyTests {
class KPropertyPathTests {
@Test
fun `Convert normal KProperty to field name`() {
val property = nestedFieldName(Book::title)
val property = asString(Book::title)
assertThat(property).isEqualTo("title")
}
@@ -34,7 +34,7 @@ class NestedPropertyTests {
@Test
fun `Convert nested KProperty to field name`() {
val property = nestedFieldName(Book::author / Author::name)
val property = asString(Book::author / Author::name)
assertThat(property).isEqualTo("author.name")
}
@@ -44,7 +44,7 @@ class NestedPropertyTests {
class Entity(val book: Book)
val property = nestedFieldName(Entity::book / Book::author / Author::name)
val property = asString(Entity::book / Book::author / Author::name)
assertThat(property).isEqualTo("book.author.name")
}
@@ -55,11 +55,11 @@ class NestedPropertyTests {
class Entity(val book: Book)
class AnotherEntity(val entity: Entity)
val property = nestedFieldName(AnotherEntity::entity / Entity::book / Book::author / Author::name)
val property = asString(AnotherEntity::entity / Entity::book / Book::author / Author::name)
assertThat(property).isEqualTo("entity.book.author.name")
}
class Book(val title: String, val author: Author)
class Author(val name: String)
}
}

View File

@@ -15,361 +15,396 @@
*/
package org.springframework.data.mongodb.core.query
import org.assertj.core.api.Assertions.*
import org.assertj.core.api.Assertions.assertThat
import org.bson.BsonRegularExpression
import org.junit.Test
import org.springframework.data.geo.Circle
import org.springframework.data.geo.Point
import org.springframework.data.mongodb.core.geo.GeoJsonPoint
import org.springframework.data.mongodb.core.schema.JsonSchemaObject.*
import org.springframework.data.mongodb.core.schema.JsonSchemaObject.Type
import java.util.regex.Pattern
/**
* @author Tjeu Kayim
* @author Mark Paluch
*/
class TypedCriteriaExtensionsTests {
@Test
fun `isEqualTo() should equal classic criteria`() {
fun `isEqualTo() should equal expected criteria`() {
val typed = Book::title isEqualTo "Moby-Dick"
val classic = Criteria("title").isEqualTo("Moby-Dick")
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").isEqualTo("Moby-Dick")
assertThat(typed).isEqualTo(expected)
}
@Test
fun `ne() should equal classic criteria`() {
fun `ne() should equal expected criteria`() {
val typed = Book::title ne "Moby-Dick"
val classic = Criteria("title").ne("Moby-Dick")
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").ne("Moby-Dick")
assertThat(typed).isEqualTo(expected)
}
@Test
fun `lt() should equal classic criteria`() {
fun `lt() should equal expected criteria`() {
val typed = Book::price lt 100
val classic = Criteria("price").lt(100)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").lt(100)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `lte() should equal classic criteria`() {
fun `lte() should equal expected criteria`() {
val typed = Book::price lte 100
val classic = Criteria("price").lte(100)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").lte(100)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `gt() should equal classic criteria`() {
fun `gt() should equal expected criteria`() {
val typed = Book::price gt 100
val classic = Criteria("price").gt(100)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").gt(100)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `gte() should equal classic criteria`() {
fun `gte() should equal expected criteria`() {
val typed = Book::price gte 100
val classic = Criteria("price").gte(100)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").gte(100)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `inValues(vararg) should equal classic criteria`() {
fun `inValues(vararg) should equal expected criteria`() {
val typed = Book::price.inValues(1, 2, 3)
val classic = Criteria("price").inValues(1, 2, 3)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").inValues(1, 2, 3)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `inValues(list) should equal classic criteria`() {
fun `inValues(list) should equal expected criteria`() {
val typed = Book::price inValues listOf(1, 2, 3)
val classic = Criteria("price").inValues(listOf(1, 2, 3))
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").inValues(listOf(1, 2, 3))
assertThat(typed).isEqualTo(expected)
}
@Test
fun `nin(vararg) should equal classic criteria`() {
fun `nin(vararg) should equal expected criteria`() {
val typed = Book::price.nin(1, 2, 3)
val classic = Criteria("price").nin(1, 2, 3)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").nin(1, 2, 3)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `nin(list) should equal classic criteria`() {
fun `nin(list) should equal expected criteria`() {
val typed = Book::price nin listOf(1, 2, 3)
val classic = Criteria("price").nin(listOf(1, 2, 3))
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").nin(listOf(1, 2, 3))
assertThat(typed).isEqualTo(expected)
}
@Test
fun `mod() should equal classic criteria`() {
fun `mod() should equal expected criteria`() {
val typed = Book::price.mod(2, 3)
val classic = Criteria("price").mod(2, 3)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").mod(2, 3)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `all(vararg) should equal classic criteria`() {
fun `all(vararg) should equal expected criteria`() {
val typed = Book::categories.all(1, 2, 3)
val classic = Criteria("categories").all(1, 2, 3)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("categories").all(1, 2, 3)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `all(list) should equal classic criteria`() {
fun `all(list) should equal expected criteria`() {
val typed = Book::categories all listOf(1, 2, 3)
val classic = Criteria("categories").all(listOf(1, 2, 3))
assertThat(typed).isEqualTo(classic)
val expected = Criteria("categories").all(listOf(1, 2, 3))
assertThat(typed).isEqualTo(expected)
}
@Test
fun `size() should equal classic criteria`() {
fun `size() should equal expected criteria`() {
val typed = Book::categories size 4
val classic = Criteria("categories").size(4)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("categories").size(4)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `exists() should equal classic criteria`() {
fun `exists() should equal expected criteria`() {
val typed = Book::title exists true
val classic = Criteria("title").exists(true)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").exists(true)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `type(Int) should equal classic criteria`() {
fun `type(Int) should equal expected criteria`() {
val typed = Book::title type 2
val classic = Criteria("title").type(2)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").type(2)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `type(List) should equal classic criteria`() {
fun `type(List) should equal expected criteria`() {
val typed = Book::title type listOf(Type.STRING, Type.BOOLEAN)
val classic = Criteria("title").type(Type.STRING, Type.BOOLEAN)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").type(Type.STRING, Type.BOOLEAN)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `type(vararg) should equal classic criteria`() {
fun `type(vararg) should equal expected criteria`() {
val typed = Book::title.type(Type.STRING, Type.BOOLEAN)
val classic = Criteria("title").type(Type.STRING, Type.BOOLEAN)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").type(Type.STRING, Type.BOOLEAN)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `not() should equal classic criteria`() {
fun `not() should equal expected criteria`() {
val typed = Book::price.not().lt(123)
val classic = Criteria("price").not().lt(123)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("price").not().lt(123)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `regex(string) should equal classic criteria`() {
fun `regex(string) should equal expected criteria`() {
val typed = Book::title regex "ab+c"
val classic = Criteria("title").regex("ab+c")
assertEqualCriteriaByJson(typed, classic)
val expected = Criteria("title").regex("ab+c")
assertEqualCriteriaByJson(typed, expected)
}
@Test
fun `regex(string, options) should equal classic criteria`() {
fun `regex(string, options) should equal expected criteria`() {
val typed = Book::title.regex("ab+c", "g")
val classic = Criteria("title").regex("ab+c", "g")
assertEqualCriteriaByJson(typed, classic)
val expected = Criteria("title").regex("ab+c", "g")
assertEqualCriteriaByJson(typed, expected)
}
@Test
fun `regex(Regex) should equal classic criteria`() {
fun `regex(Regex) should equal expected criteria`() {
val typed = Book::title regex Regex("ab+c")
val classic = Criteria("title").regex(Pattern.compile("ab+c"))
assertEqualCriteriaByJson(typed, classic)
val expected = Criteria("title").regex(Pattern.compile("ab+c"))
assertEqualCriteriaByJson(typed, expected)
}
private fun assertEqualCriteriaByJson(typed: Criteria, classic: Criteria) {
assertThat(typed.criteriaObject.toJson()).isEqualTo(classic.criteriaObject.toJson())
private fun assertEqualCriteriaByJson(typed: Criteria, expected: Criteria) {
assertThat(typed.criteriaObject.toJson()).isEqualTo(expected.criteriaObject.toJson())
}
@Test
fun `regex(Pattern) should equal classic criteria`() {
fun `regex(Pattern) should equal expected criteria`() {
val value = Pattern.compile("ab+c")
val typed = Book::title regex value
val classic = Criteria("title").regex(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").regex(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `regex(BsonRegularExpression) should equal classic criteria`() {
fun `regex(BsonRegularExpression) should equal expected criteria`() {
val expression = BsonRegularExpression("ab+c")
val typed = Book::title regex expression
val classic = Criteria("title").regex(expression)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").regex(expression)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `withinSphere() should equal classic criteria`() {
fun `withinSphere() should equal expected criteria`() {
val value = Circle(Point(928.76, 28.345), 65.243)
val typed = Building::location withinSphere value
val classic = Criteria("location").withinSphere(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").withinSphere(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `within() should equal classic criteria`() {
fun `within() should equal expected criteria`() {
val value = Circle(Point(5.43421, 12.456), 52.67)
val typed = Building::location within value
val classic = Criteria("location").within(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").within(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `near() should equal classic criteria`() {
fun `near() should equal expected criteria`() {
val value = Point(57.431, 71.345)
val typed = Building::location near value
val classic = Criteria("location").near(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").near(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `nearSphere() should equal classic criteria`() {
fun `nearSphere() should equal expected criteria`() {
val value = Point(5.4321, 12.345)
val typed = Building::location nearSphere value
val classic = Criteria("location").nearSphere(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").nearSphere(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `intersects() should equal classic criteria`() {
fun `intersects() should equal expected criteria`() {
val value = GeoJsonPoint(5.481573, 51.451726)
val typed = Building::location intersects value
val classic = Criteria("location").intersects(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").intersects(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `maxDistance() should equal classic criteria`() {
fun `maxDistance() should equal expected criteria`() {
val typed = Building::location maxDistance 3.0
val classic = Criteria("location").maxDistance(3.0)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").maxDistance(3.0)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `minDistance() should equal classic criteria`() {
fun `minDistance() should equal expected criteria`() {
val typed = Building::location minDistance 3.0
val classic = Criteria("location").minDistance(3.0)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("location").minDistance(3.0)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `elemMatch() should equal classic criteria`() {
fun `elemMatch() should equal expected criteria`() {
val value = Criteria("price").lt(950)
val typed = Book::title elemMatch value
val classic = Criteria("title").elemMatch(value)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").elemMatch(value)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `elemMatch(TypedCriteria) should equal classic criteria`() {
fun `elemMatch(TypedCriteria) should equal expected criteria`() {
val typed = Book::title elemMatch (Book::price lt 950)
val classic = Criteria("title").elemMatch(Criteria("price").lt(950))
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").elemMatch(Criteria("price").lt(950))
assertThat(typed).isEqualTo(expected)
}
@Test
fun `bits() should equal classic criteria`() {
fun `bits() should equal expected criteria`() {
val typed = Book::title bits { allClear(123) }
val classic = Criteria("title").bits().allClear(123)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").bits().allClear(123)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `One level nested should equal classic criteria`() {
fun `One level nested should equal expected criteria`() {
val typed = Book::author / Author::name isEqualTo "Herman Melville"
val expected = Criteria("author.name").isEqualTo("Herman Melville")
val classic = Criteria("author.name").isEqualTo("Herman Melville")
assertThat(typed).isEqualTo(classic)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `Two levels nested should equal classic criteria`() {
fun `Two levels nested should equal expected criteria`() {
data class Entity(val book: Book)
val typed = Entity::book / Book::author / Author::name isEqualTo "Herman Melville"
val classic = Criteria("book.author.name").isEqualTo("Herman Melville")
assertThat(typed).isEqualTo(classic)
val expected = Criteria("book.author.name").isEqualTo("Herman Melville")
assertThat(typed).isEqualTo(expected)
}
@Test
fun `typed criteria inside orOperator() should equal classic criteria`() {
fun `typed criteria inside orOperator() should equal expected criteria`() {
val typed = (Book::title isEqualTo "Moby-Dick").orOperator(
Book::price lt 1200,
Book::price gt 240
Book::price lt 1200,
Book::price gt 240
)
val classic = Criteria("title").isEqualTo("Moby-Dick")
.orOperator(
Criteria("price").lt(1200),
Criteria("price").gt(240)
)
assertThat(typed).isEqualTo(classic)
val expected = Criteria("title").isEqualTo("Moby-Dick")
.orOperator(
Criteria("price").lt(1200),
Criteria("price").gt(240)
)
assertThat(typed).isEqualTo(expected)
}
@Test
fun `chaining gt & isEqualTo() should equal classic criteria`() {
fun `chaining gt & isEqualTo() should equal expected criteria`() {
val typed = (Book::title isEqualTo "Moby-Dick")
.and(Book::price).lt(950)
val classic = Criteria("title").isEqualTo("Moby-Dick")
.and("price").lt(950)
assertThat(typed).isEqualTo(classic)
.and(Book::price).lt(950)
val expected = Criteria("title").isEqualTo("Moby-Dick")
.and("price").lt(950)
assertThat(typed).isEqualTo(expected)
}
data class Book(
val title: String = "Moby-Dick",
val price: Int = 123,
val available: Boolean = true,
val categories: List<String> = emptyList(),
val author: Author = Author()
val title: String = "Moby-Dick",
val price: Int = 123,
val available: Boolean = true,
val categories: List<String> = emptyList(),
val author: Author = Author()
)
data class Author(
val name: String = "Herman Melville"
val name: String = "Herman Melville"
)
data class Building(
val location: GeoJsonPoint
val location: GeoJsonPoint
)
}
}

View File

@@ -1,6 +1,10 @@
[[new-features]]
= New & Noteworthy
[[new-features.2-2-0]]
== What's New in Spring Data MongoDB 2.2
* <<mongo.query.kotlin-support,Type-safe Queries for Kotlin>>
[[new-features.2-1-0]]
== What's New in Spring Data MongoDB 2.1

View File

@@ -1795,10 +1795,12 @@ GeoResults<Jedi> results = mongoOps.query(SWCharacter.class)
[[mongo.query.kotlin-support]]
=== Type-safe Queries for Kotlin
https://kotlinlang.org/docs/reference/reflection.html#property-references[Kotlin property references]
can be used to build type-safe queries.
Kotlin embraces domain-specific language creation through its language syntax and its extension system.
Spring Data MongoDB ships with a Kotlin Extension for `Criteria` using https://kotlinlang.org/docs/reference/reflection.html#property-references[Kotlin property references] to build type-safe queries.
Queries using this extension are typically benefit from improved readability.
Most keywords on `Criteria` have a matching Kotlin extension, such as `inValues` and `regex`.
Most methods in `Criteria` have a matching Kotlin extension, like `inValues` and `regex`.
Consider the following example explaining Type-safe Queries:
====
[source,kotlin]
@@ -1806,21 +1808,29 @@ Most methods in `Criteria` have a matching Kotlin extension, like `inValues` and
import org.springframework.data.mongodb.core.query.*
mongoOperations.find<Book>(
Query(Book::title isEqualTo "Moby-Dick") <1>
Query(Book::title isEqualTo "Moby-Dick") <1>
)
Book::title exists true
mongoOperations.find<Book>(
Query(titlePredicate = Book::title exists true)
)
Criteria().andOperator(
Book::price gt 5,
Book::price lt 10
mongoOperations.find<Book>(
Criteria().andOperator(
Book::price gt 5,
Book::price lt 10
)
)
// Binary operators
BinaryMessage::payload bits { allClear(0b101) } <2>
mongoOperations.find<BinaryMessage>(
Query(BinaryMessage::payload bits { allClear(0b101) }) <2>
)
// Nested Properties (i.e. refer to "book.author")
Book::author / Author::name regex "^H" <3>
mongoOperations.find<Book>(
Query(Book::author / Author::name regex "^H") <3>
)
----
<1> `isEqualTo()` is an infix extension function with receiver type `KProperty<T>` that returns `Criteria`.
<2> For bitwise operators, pass a lambda argument where you call one of the methods of `Criteria.BitwiseCriteriaOperators`.