From 2ca879f534e047c6f0a48a44bc16b68494a32dd0 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Mon, 10 Dec 2018 15:47:22 +0100 Subject: [PATCH] DATAMONGO-2138 - Polishing. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../mongodb/core/query/CriteriaExtensions.kt | 15 +- .../{NestedProperty.kt => KPropertyPath.kt} | 19 +- .../core/query/TypedCriteriaExtensions.kt | 78 ++--- ...PropertyTests.kt => KPropertyPathTests.kt} | 12 +- .../query/TypedCriteriaExtensionsTests.kt | 303 ++++++++++-------- src/main/asciidoc/new-features.adoc | 4 + src/main/asciidoc/reference/mongodb.adoc | 30 +- 7 files changed, 256 insertions(+), 205 deletions(-) rename spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/{NestedProperty.kt => KPropertyPath.kt} (75%) rename spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/{NestedPropertyTests.kt => KPropertyPathTests.kt} (81%) diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/CriteriaExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/CriteriaExtensions.kt index a36deec24..815b24d22 100644 --- a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/CriteriaExtensions.kt +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/CriteriaExtensions.kt @@ -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 Criteria.inValues(c: Collection) : Criteria = `in`(c) +fun Criteria.inValues(c: Collection): Criteria = `in`(c) /** * Extension for [Criteria.in] providing an `inValues` alias since `in` is a reserved keyword in Kotlin. @@ -39,19 +39,20 @@ fun Criteria.inValues(c: Collection) : 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)) diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/NestedProperty.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/KPropertyPath.kt similarity index 75% rename from spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/NestedProperty.kt rename to spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/KPropertyPath.kt index 3fba3cf3a..48f4ab124 100644 --- a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/NestedProperty.kt +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/KPropertyPath.kt @@ -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( - internal val parent: KProperty, - internal val child: KProperty1 +class KPropertyPath( + internal val parent: KProperty, + internal val child: KProperty1 ) : KProperty 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 KProperty.div(other: KProperty1) = - NestedProperty(this, other) + KPropertyPath(this, other) diff --git a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensions.kt b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensions.kt index 6e7654aef..39d7151b4 100644 --- a/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensions.kt +++ b/spring-data-mongodb/src/main/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensions.kt @@ -31,7 +31,7 @@ import kotlin.reflect.KProperty * @see Criteria.isEqualTo */ infix fun KProperty.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 KProperty.isEqualTo(value: T) = * @see Criteria.ne */ infix fun KProperty.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 KProperty.ne(value: T): Criteria = * @see Criteria.lt */ infix fun KProperty.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 KProperty.lt(value: T): Criteria = * @see Criteria.lte */ infix fun KProperty.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 KProperty.lte(value: T): Criteria = * @see Criteria.gt */ infix fun KProperty.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 KProperty.gt(value: T): Criteria = * @see Criteria.gte */ infix fun KProperty.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 KProperty.gte(value: T): Criteria = * @see Criteria.inValues */ fun KProperty.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 KProperty.inValues(vararg o: Any): Criteria = * @see Criteria.inValues */ infix fun KProperty.inValues(value: Collection): Criteria = - Criteria(nestedFieldName(this)).`in`(value) + Criteria(asString(this)).`in`(value) /** * Creates a criterion using the $nin operator. @@ -119,7 +119,7 @@ infix fun KProperty.inValues(value: Collection): Criteria = * @see Criteria.nin */ fun KProperty.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 KProperty.nin(vararg o: Any): Criteria = * @see Criteria.nin */ infix fun KProperty.nin(value: Collection): Criteria = - Criteria(nestedFieldName(this)).nin(value) + Criteria(asString(this)).nin(value) /** * Creates a criterion using the $mod operator. @@ -141,7 +141,7 @@ infix fun KProperty.nin(value: Collection): Criteria = * @see Criteria.mod */ fun KProperty.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.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): 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): 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.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.regex(re: String): Criteria = * @see Criteria.regex */ fun KProperty.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.regex(re: String, options: String?): Criteria = * @see Criteria.regex */ infix fun KProperty.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.regex(re: Regex): Criteria = * @see Criteria.regex */ infix fun KProperty.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.regex(re: Pattern): Criteria = * @see Criteria.regex */ infix fun KProperty.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>.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>.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>.within(shape: Shape): Criteria = * @see Criteria.near */ infix fun KProperty>.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>.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>.nearSphere(point: Point): Criteria = * @see Criteria.intersects */ infix fun KProperty>.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>.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>.maxDistance(d: Double): Criteria = * @see Criteria.minDistance */ infix fun KProperty>.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) diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/NestedPropertyTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/KPropertyPathTests.kt similarity index 81% rename from spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/NestedPropertyTests.kt rename to spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/KPropertyPathTests.kt index 40f4fd8c6..1836c952c 100644 --- a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/NestedPropertyTests.kt +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/KPropertyPathTests.kt @@ -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) -} \ No newline at end of file +} diff --git a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensionsTests.kt b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensionsTests.kt index 75ae0d863..bec2b2d98 100644 --- a/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensionsTests.kt +++ b/spring-data-mongodb/src/test/kotlin/org/springframework/data/mongodb/core/query/TypedCriteriaExtensionsTests.kt @@ -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 = emptyList(), - val author: Author = Author() + val title: String = "Moby-Dick", + val price: Int = 123, + val available: Boolean = true, + val categories: List = 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 ) -} \ No newline at end of file +} diff --git a/src/main/asciidoc/new-features.adoc b/src/main/asciidoc/new-features.adoc index 184b514b3..2439883e4 100644 --- a/src/main/asciidoc/new-features.adoc +++ b/src/main/asciidoc/new-features.adoc @@ -1,6 +1,10 @@ [[new-features]] = New & Noteworthy +[[new-features.2-2-0]] +== What's New in Spring Data MongoDB 2.2 +* <> + [[new-features.2-1-0]] == What's New in Spring Data MongoDB 2.1 diff --git a/src/main/asciidoc/reference/mongodb.adoc b/src/main/asciidoc/reference/mongodb.adoc index cfef0d875..0f5a11211 100644 --- a/src/main/asciidoc/reference/mongodb.adoc +++ b/src/main/asciidoc/reference/mongodb.adoc @@ -1795,10 +1795,12 @@ GeoResults 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( - Query(Book::title isEqualTo "Moby-Dick") <1> + Query(Book::title isEqualTo "Moby-Dick") <1> ) -Book::title exists true +mongoOperations.find( + Query(titlePredicate = Book::title exists true) +) -Criteria().andOperator( - Book::price gt 5, - Book::price lt 10 +mongoOperations.find( + Criteria().andOperator( + Book::price gt 5, + Book::price lt 10 + ) ) // Binary operators -BinaryMessage::payload bits { allClear(0b101) } <2> +mongoOperations.find( + Query(BinaryMessage::payload bits { allClear(0b101) }) <2> +) // Nested Properties (i.e. refer to "book.author") -Book::author / Author::name regex "^H" <3> +mongoOperations.find( + Query(Book::author / Author::name regex "^H") <3> +) ---- <1> `isEqualTo()` is an infix extension function with receiver type `KProperty` that returns `Criteria`. <2> For bitwise operators, pass a lambda argument where you call one of the methods of `Criteria.BitwiseCriteriaOperators`.