From 019975a5c81f9a3fcbd71649a1c623314b01f5f0 Mon Sep 17 00:00:00 2001 From: Jake Son Date: Sat, 14 Oct 2023 20:11:18 +0900 Subject: [PATCH 1/4] feat: implement group stage dsl --- .../core/aggregation/AggregationDsl.kt | 11 ++ .../mongodb/core/aggregation/GroupStageDsl.kt | 53 ++++++++ .../core/aggregation/AggregationDslTest.kt | 16 +++ .../core/aggregation/GroupStageDslTest.kt | 128 ++++++++++++++++++ 4 files changed, 208 insertions(+) create mode 100644 core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt create mode 100644 core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDsl.kt index f31da098..cd7fe7bb 100644 --- a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDsl.kt +++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDsl.kt @@ -277,6 +277,17 @@ class AggregationDsl { operations += Aggregation.limit(maxElements) } + /** + * Configures a stage that separates documents into groups according to a "group key". + * The output is one document for each unique group key. + * + * @param configuration The configuration block for the [GroupStageDsl]. + * @see $group (aggregation) + */ + fun group(configuration: GroupStageDsl.() -> Unit) { + operations += GroupStageDsl().apply(configuration).build() + } + /** * Builds the [Aggregation] using the configured [AggregationOperation]s. * diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt new file mode 100644 index 00000000..1e83f528 --- /dev/null +++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt @@ -0,0 +1,53 @@ +package com.github.inflab.spring.data.mongodb.core.aggregation + +import com.github.inflab.spring.data.mongodb.core.aggregation.expression.AggregationExpressionDsl +import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker +import com.github.inflab.spring.data.mongodb.core.extension.toDotPath +import org.bson.Document +import org.springframework.data.mongodb.core.aggregation.AggregationExpression +import org.springframework.data.mongodb.core.aggregation.AggregationOperation +import kotlin.reflect.KProperty + +/** + * A Kotlin DSL to configure $group stage using idiomatic Kotlin code. + * + * @author Jake Son + * @since 1.0 + * @see $group (aggregation) + */ +@AggregationMarker +class GroupStageDsl { + private var document = Document() + private val accumulators: MutableMap = mutableMapOf() + + fun _idNull() { + document["_id"] = null + } + + fun _id(path: String) { + document["_id"] = "$$path" + } + + fun _id(property: KProperty<*>) { + _id(property.toDotPath()) + } + + fun _id(configuration: AggregationExpressionDsl.() -> AggregationExpression) { + document["_id"] = AggregationExpressionDsl().configuration().toDocument() + } + + infix fun String.accumulator(configuration: AggregationExpressionDsl.() -> AggregationExpression) { + accumulators[this] = AggregationExpressionDsl().configuration() + } + + internal fun build() = AggregationOperation { context -> + (document["_id"] as? AggregationExpression)?.let { + document["_id"] = it.toDocument(context) + } + accumulators.forEach { (key, value) -> + document[key] = value.toDocument(context) + } + + Document("\$group", document) + } +} diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt index fc85efa4..fb93c260 100644 --- a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt +++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt @@ -313,4 +313,20 @@ internal class AggregationDslTest : FreeSpec({ ).toString() } } + + "group" - { + "should create group stage" { + // when + val aggregation = aggregation { + group { + _idNull() + } + } + + // then + aggregation.toString() shouldBe Aggregation.newAggregation( + Aggregation.group(), + ).toString() + } + } }) diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt new file mode 100644 index 00000000..e6632bec --- /dev/null +++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt @@ -0,0 +1,128 @@ +package com.github.inflab.spring.data.mongodb.core.aggregation + +import com.github.inflab.spring.data.mongodb.core.util.shouldBeJson +import io.kotest.core.spec.style.FreeSpec + +internal class GroupStageDslTest : FreeSpec({ + fun group(block: GroupStageDsl.() -> Unit) = + GroupStageDsl().apply(block) + + "_id" - { + "should build by null" { + // when + val stage = group { + _idNull() + } + + // then + val result = stage.build() + + // then + result.shouldBeJson( + """ + { + "${'$'}group": { + "_id": null + } + } + """.trimIndent(), + ) + } + + "should build by string" { + // given + val path = "path" + val stage = group { + _id(path) + } + + // when + val result = stage.build() + + // then + result.shouldBeJson( + """ + { + "${'$'}group": { + "_id": "${'$'}path" + } + } + """.trimIndent(), + ) + } + + "should build by property" { + // given + data class Test(val property: String) + val stage = group { + _id(Test::property) + } + + // when + val result = stage.build() + + // then + result.shouldBeJson( + """ + { + "${'$'}group": { + "_id": "${'$'}property" + } + } + """.trimIndent(), + ) + } + + "should build by expression" { + // given + val stage = group { + _id { + abs("field") + } + } + + // when + val result = stage.build() + + // then + result.shouldBeJson( + """ + { + "${'$'}group": { + "_id": { + "${'$'}abs": "${'$'}field" + } + } + } + """.trimIndent(), + ) + } + } + + "accumulator" - { + "should build by expression" { + // given + val stage = group { + "amount" accumulator { + abs("field") + } + } + + // when + val result = stage.build() + + // then + result.shouldBeJson( + """ + { + "${'$'}group": { + "amount": { + "${'$'}abs": "${'$'}field" + } + } + } + """.trimIndent(), + ) + } + } +}) From 33fe5e70adc8c78bd8ada834ce713bd7150f77dc Mon Sep 17 00:00:00 2001 From: Jake Son Date: Sat, 14 Oct 2023 20:59:27 +0900 Subject: [PATCH 2/4] test: add group stage examples --- .../expression/AggregationExpressionDsl.kt | 8 ++ .../AggregationExpressionDslTest.kt | 16 +++ .../mongodb/repository/GroupRepository.kt | 111 +++++++++++++++++ .../mongodb/repository/GroupRepositoryTest.kt | 114 ++++++++++++++++++ 4 files changed, 249 insertions(+) create mode 100644 example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt create mode 100644 example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepositoryTest.kt diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDsl.kt index d59bd902..e3a73366 100644 --- a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDsl.kt +++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDsl.kt @@ -5,6 +5,7 @@ import com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithme import com.github.inflab.spring.data.mongodb.core.aggregation.expression.arithmetic.SubtractExpressionDsl import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker import com.github.inflab.spring.data.mongodb.core.extension.toDotPath +import org.bson.Document import org.springframework.data.mongodb.core.aggregation.AggregationExpression import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators import kotlin.reflect.KProperty @@ -155,4 +156,11 @@ class AggregationExpressionDsl { */ fun subtract(configuration: SubtractExpressionDsl.() -> AggregationExpression): AggregationExpression = SubtractExpressionDsl().configuration() + + /** + * Returns the number of documents in a group. + */ + fun count() = AggregationExpression { + Document("\$count", Document()) + } } diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDslTest.kt index 3c7146f7..cb2de448 100644 --- a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDslTest.kt +++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/expression/AggregationExpressionDslTest.kt @@ -303,4 +303,20 @@ internal class AggregationExpressionDslTest : FreeSpec({ ) } } + + "count" - { + "should build an expression" { + // when + val result = expression { count() } + + // then + result.shouldBeJson( + """ + { + "${'$'}count": {} + } + """.trimIndent(), + ) + } + } }) diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt new file mode 100644 index 00000000..560ab603 --- /dev/null +++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt @@ -0,0 +1,111 @@ +package com.github.inflab.example.spring.data.mongodb.repository + +import com.github.inflab.spring.data.mongodb.core.aggregation.aggregation +import org.springframework.data.mongodb.core.MongoTemplate +import org.springframework.data.mongodb.core.aggregate +import org.springframework.data.mongodb.core.aggregation.AccumulatorOperators +import org.springframework.data.mongodb.core.aggregation.AggregationExpression +import org.springframework.data.mongodb.core.aggregation.AggregationResults +import org.springframework.data.mongodb.core.aggregation.ArithmeticOperators +import org.springframework.data.mongodb.core.aggregation.DateOperators +import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.query.Criteria +import org.springframework.stereotype.Repository +import java.time.LocalDateTime + +@Repository +class GroupRepository( + private val mongoTemplate: MongoTemplate, +) { + + @Document("sales") + data class Sales(val id: Long, val item: String, val price: Double, val quantity: Int, val date: LocalDateTime) + + data class CountDto(val id: Long?, val count: Int) + + /** + * @see Count the Number of Documents in a Collection + */ + fun count(): AggregationResults { + val aggregation = aggregation { + group { + _idNull() + "count" accumulator { + count() + } + } + } + + return mongoTemplate.aggregate(aggregation) + } + + data class GroupByItemHavingDto(val id: String, val totalSaleAmount: Double) + + /** + * @see Group by Item Having + */ + fun groupByItemHaving(): AggregationResults { + val aggregation = aggregation { + group { + _id(Sales::item) + "totalSaleAmount" accumulator { + // TODO: add $sum operator + AccumulatorOperators.Sum.sumOf( + ArithmeticOperators.Multiply.valueOf(Sales::price.name).multiplyBy(Sales::quantity.name), + ) + } + } + // TODO: add $match operator + match(Criteria.where("totalSaleAmount").gt(100)) + } + + return mongoTemplate.aggregate(aggregation) + } + + data class GroupByDayAndYearDto( + val id: String, + val totalSaleAmount: Double, + val averageQuantity: Double, + val count: Int, + ) + + /** + * @see Group by Day and Year + */ + fun groupByDayAndYear(): AggregationResults { + val aggregation = aggregation { + // TODO: add $match operator + match( + Criteria.where(Sales::date.name).gte(LocalDateTime.of(2014, 1, 1, 0, 0, 0)) + .lt(LocalDateTime.of(2015, 1, 1, 0, 0, 0)), + ) + + group { + _id { + // TODO: add $dayOfYear operator + DateOperators.DateToString.dateOf(Sales::date.name).toString("%Y-%m-%d") + } + "totalSaleAmount" accumulator { + // TODO: add $sum operator + AccumulatorOperators.Sum.sumOf( + ArithmeticOperators.Multiply.valueOf(Sales::price.name).multiplyBy(Sales::quantity.name), + ) + } + "averageQuantity" accumulator { + // TODO: add $avg operator + AccumulatorOperators.Avg.avgOf(Sales::quantity.name) + } + "count" accumulator { + // TODO: add $sum operator + AggregationExpression { org.bson.Document("\$sum", 1) } + } + } + + sort { + "totalSaleAmount" by desc + } + } + + return mongoTemplate.aggregate(aggregation) + } +} diff --git a/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepositoryTest.kt b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepositoryTest.kt new file mode 100644 index 00000000..5b1a97df --- /dev/null +++ b/example/spring-data-mongodb/src/test/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepositoryTest.kt @@ -0,0 +1,114 @@ +package com.github.inflab.example.spring.data.mongodb.repository + +import com.github.inflab.example.spring.data.mongodb.extension.makeMongoTemplate +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import java.time.LocalDateTime + +internal class GroupRepositoryTest : FreeSpec({ + val mongoTemplate = makeMongoTemplate() + val groupRepository = GroupRepository(mongoTemplate) + + beforeSpec { + val sales = listOf( + GroupRepository.Sales( + id = 1, + item = "abc", + price = 10.0, + quantity = 2, + date = LocalDateTime.of(2014, 3, 1, 8, 0, 0), + ), + GroupRepository.Sales( + id = 2, + item = "jkl", + price = 20.0, + quantity = 1, + date = LocalDateTime.of(2014, 3, 1, 9, 0, 0), + ), + GroupRepository.Sales( + id = 3, + item = "xyz", + price = 5.0, + quantity = 10, + date = LocalDateTime.of(2014, 3, 15, 9, 0, 0), + ), + GroupRepository.Sales( + id = 4, + item = "xyz", + price = 5.0, + quantity = 20, + date = LocalDateTime.of(2014, 4, 4, 11, 21, 39, 736000000), + ), + GroupRepository.Sales( + id = 5, + item = "abc", + price = 10.0, + quantity = 10, + date = LocalDateTime.of(2014, 4, 4, 21, 23, 13, 331000000), + ), + GroupRepository.Sales( + id = 6, + item = "def", + price = 7.5, + quantity = 5, + date = LocalDateTime.of(2015, 6, 4, 5, 8, 13), + ), + GroupRepository.Sales( + id = 7, + item = "def", + price = 7.5, + quantity = 10, + date = LocalDateTime.of(2015, 9, 10, 8, 43, 0), + ), + GroupRepository.Sales( + id = 8, + item = "abc", + price = 10.0, + quantity = 5, + date = LocalDateTime.of(2016, 2, 6, 20, 20, 13), + ), + ) + mongoTemplate.insertAll(sales) + } + + "count" { + // when + val result = groupRepository.count() + + // then + result.mappedResults.map { it.count } shouldBe listOf(8) + } + + "groupByItemHaving" { + // when + val result = groupRepository.groupByItemHaving() + + // then + result.mappedResults.toSet() shouldBe setOf( + GroupRepository.GroupByItemHavingDto(id = "abc", totalSaleAmount = 170.0), + GroupRepository.GroupByItemHavingDto(id = "xyz", totalSaleAmount = 150.0), + GroupRepository.GroupByItemHavingDto(id = "def", totalSaleAmount = 112.5), + ) + } + + "groupByDayAndYear" { + // when + val result = groupRepository.groupByDayAndYear() + + // then + result.mappedResults.take(2) shouldBe listOf( + GroupRepository.GroupByDayAndYearDto( + id = "2014-04-04", + totalSaleAmount = 200.0, + averageQuantity = 15.0, + count = 2, + ), + GroupRepository.GroupByDayAndYearDto( + id = "2014-03-15", + totalSaleAmount = 50.0, + averageQuantity = 10.0, + count = 1, + ), + ) + } +}) From d9b3350f3d1c2b9fd212a744345481ab4fbabe5b Mon Sep 17 00:00:00 2001 From: Jake Son Date: Sat, 14 Oct 2023 21:02:08 +0900 Subject: [PATCH 3/4] refactor: move entity file to repository --- .../mongodb/entity/sample/FruitQuantities.kt | 7 ----- .../data/mongodb/entity/sample/Fruits.kt | 13 --------- .../data/mongodb/entity/sample/UserAccount.kt | 10 ------- .../data/mongodb/entity/sample/Users.kt | 22 --------------- .../atlas/EqualsSearchRepository.kt | 27 ++++++++++++++++++- .../atlas/ExistsSearchRepository.kt | 18 ++++++++++++- 6 files changed, 43 insertions(+), 54 deletions(-) delete mode 100644 example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/FruitQuantities.kt delete mode 100644 example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Fruits.kt delete mode 100644 example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/UserAccount.kt delete mode 100644 example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Users.kt diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/FruitQuantities.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/FruitQuantities.kt deleted file mode 100644 index c55fd158..00000000 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/FruitQuantities.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.inflab.example.spring.data.mongodb.entity.sample - -data class FruitQuantities( - val lemons: Int, - val oranges: Int, - val grapefruit: Int, -) diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Fruits.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Fruits.kt deleted file mode 100644 index a29abb4d..00000000 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Fruits.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.github.inflab.example.spring.data.mongodb.entity.sample - -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document - -@Document("fruits") -data class Fruits( - @Id - val id: String, - val type: String?, - val description: String, - val quantities: FruitQuantities?, -) diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/UserAccount.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/UserAccount.kt deleted file mode 100644 index b6ffa600..00000000 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/UserAccount.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.github.inflab.example.spring.data.mongodb.entity.sample - -import org.springframework.data.mongodb.core.mapping.Field - -data class UserAccount( - @Field("new_user") - val newUser: Boolean, - @Field("active_user") - val activeUser: Boolean, -) diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Users.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Users.kt deleted file mode 100644 index a0601194..00000000 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/entity/sample/Users.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.inflab.example.spring.data.mongodb.entity.sample - -import org.springframework.data.annotation.Id -import org.springframework.data.mongodb.core.mapping.Document -import org.springframework.data.mongodb.core.mapping.Field -import java.time.LocalDateTime - -@Document("users") -data class Users( - @Id - val id: String, - val name: String, - @Field("verified_user") - val verifiedUser: Boolean, - val account: UserAccount, - val teammates: List, - val region: String, - @Field("account_created") - val accountCreated: LocalDateTime, - @Field("employee_number") - val employeeNumber: Int, -) diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/EqualsSearchRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/EqualsSearchRepository.kt index 1ca7782b..e4c4fb05 100644 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/EqualsSearchRepository.kt +++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/EqualsSearchRepository.kt @@ -1,11 +1,13 @@ package com.github.inflab.example.spring.data.mongodb.repository.atlas -import com.github.inflab.example.spring.data.mongodb.entity.sample.Users import com.github.inflab.spring.data.mongodb.core.aggregation.aggregation import org.bson.types.ObjectId +import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.aggregate import org.springframework.data.mongodb.core.aggregation.AggregationResults +import org.springframework.data.mongodb.core.mapping.Document +import org.springframework.data.mongodb.core.mapping.Field import org.springframework.stereotype.Repository import java.time.LocalDateTime @@ -14,6 +16,29 @@ class EqualsSearchRepository( private val mongoTemplate: MongoTemplate, ) { + data class UserAccount( + @Field("new_user") + val newUser: Boolean, + @Field("active_user") + val activeUser: Boolean, + ) + + @Document("users") + data class Users( + @Id + val id: String, + val name: String, + @Field("verified_user") + val verifiedUser: Boolean, + val account: UserAccount, + val teammates: List, + val region: String, + @Field("account_created") + val accountCreated: LocalDateTime, + @Field("employee_number") + val employeeNumber: Int, + ) + data class NameAndScoreDto(val name: String, val score: Double) /** diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/ExistsSearchRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/ExistsSearchRepository.kt index d12616ca..d7c675e2 100644 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/ExistsSearchRepository.kt +++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/atlas/ExistsSearchRepository.kt @@ -1,10 +1,11 @@ package com.github.inflab.example.spring.data.mongodb.repository.atlas -import com.github.inflab.example.spring.data.mongodb.entity.sample.Fruits import com.github.inflab.spring.data.mongodb.core.aggregation.aggregation +import org.springframework.data.annotation.Id import org.springframework.data.mongodb.core.MongoTemplate import org.springframework.data.mongodb.core.aggregate import org.springframework.data.mongodb.core.aggregation.AggregationResults +import org.springframework.data.mongodb.core.mapping.Document import org.springframework.stereotype.Repository @Repository @@ -12,6 +13,21 @@ class ExistsSearchRepository( private val mongoTemplate: MongoTemplate, ) { + data class FruitQuantities( + val lemons: Int, + val oranges: Int, + val grapefruit: Int, + ) + + @Document("fruits") + data class Fruits( + @Id + val id: String, + val type: String?, + val description: String, + val quantities: FruitQuantities?, + ) + /** * @see Compound Example */ From 3b85f1de8eab1d4a35588648b2ee2c247939aca8 Mon Sep 17 00:00:00 2001 From: Jake Son Date: Sat, 14 Oct 2023 21:07:19 +0900 Subject: [PATCH 4/4] fix: add missing documentation --- .../mongodb/core/aggregation/GroupStageDsl.kt | 34 ++++++++++++++++--- .../core/aggregation/AggregationDslTest.kt | 2 +- .../core/aggregation/GroupStageDslTest.kt | 8 ++--- .../mongodb/repository/GroupRepository.kt | 6 ++-- 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt index 1e83f528..bcebca04 100644 --- a/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt +++ b/core/src/main/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDsl.kt @@ -20,22 +20,46 @@ class GroupStageDsl { private var document = Document() private val accumulators: MutableMap = mutableMapOf() - fun _idNull() { + /** + * Specifies the group key to `null`. + * If you specify an _id value of null, or any other constant value, the $group stage returns a single document that aggregates values across all of the input documents. + */ + fun idNull() { document["_id"] = null } - fun _id(path: String) { + /** + * Specifies the group key to a field path. + * + * @param path The field name. + */ + fun id(path: String) { document["_id"] = "$$path" } - fun _id(property: KProperty<*>) { - _id(property.toDotPath()) + /** + * Specifies the group key to a field path. + * + * @param property The property reference. + */ + fun id(property: KProperty<*>) { + id(property.toDotPath()) } - fun _id(configuration: AggregationExpressionDsl.() -> AggregationExpression) { + /** + * Specifies the group key to an [AggregationExpression]. + * + * @param configuration The configuration block for the [AggregationExpression]. + */ + fun id(configuration: AggregationExpressionDsl.() -> AggregationExpression) { document["_id"] = AggregationExpressionDsl().configuration().toDocument() } + /** + * Computed using the accumulator operators. + * + * @see accumulator operators + */ infix fun String.accumulator(configuration: AggregationExpressionDsl.() -> AggregationExpression) { accumulators[this] = AggregationExpressionDsl().configuration() } diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt index fb93c260..4dd23b69 100644 --- a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt +++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/AggregationDslTest.kt @@ -319,7 +319,7 @@ internal class AggregationDslTest : FreeSpec({ // when val aggregation = aggregation { group { - _idNull() + idNull() } } diff --git a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt index e6632bec..c3134d89 100644 --- a/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt +++ b/core/src/test/kotlin/com/github/inflab/spring/data/mongodb/core/aggregation/GroupStageDslTest.kt @@ -11,7 +11,7 @@ internal class GroupStageDslTest : FreeSpec({ "should build by null" { // when val stage = group { - _idNull() + idNull() } // then @@ -33,7 +33,7 @@ internal class GroupStageDslTest : FreeSpec({ // given val path = "path" val stage = group { - _id(path) + id(path) } // when @@ -55,7 +55,7 @@ internal class GroupStageDslTest : FreeSpec({ // given data class Test(val property: String) val stage = group { - _id(Test::property) + id(Test::property) } // when @@ -76,7 +76,7 @@ internal class GroupStageDslTest : FreeSpec({ "should build by expression" { // given val stage = group { - _id { + id { abs("field") } } diff --git a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt index 560ab603..c391176c 100644 --- a/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt +++ b/example/spring-data-mongodb/src/main/kotlin/com/github/inflab/example/spring/data/mongodb/repository/GroupRepository.kt @@ -29,7 +29,7 @@ class GroupRepository( fun count(): AggregationResults { val aggregation = aggregation { group { - _idNull() + idNull() "count" accumulator { count() } @@ -47,7 +47,7 @@ class GroupRepository( fun groupByItemHaving(): AggregationResults { val aggregation = aggregation { group { - _id(Sales::item) + id(Sales::item) "totalSaleAmount" accumulator { // TODO: add $sum operator AccumulatorOperators.Sum.sumOf( @@ -81,7 +81,7 @@ class GroupRepository( ) group { - _id { + id { // TODO: add $dayOfYear operator DateOperators.DateToString.dateOf(Sales::date.name).toString("%Y-%m-%d") }