Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement Group stage #97

Merged
merged 4 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/group">$group (aggregation)</a>
*/
fun group(configuration: GroupStageDsl.() -> Unit) {
operations += GroupStageDsl().apply(configuration).build()
}

/**
* Builds the [Aggregation] using the configured [AggregationOperation]s.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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 <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/group">$group (aggregation)</a>
*/
@AggregationMarker
class GroupStageDsl {
private var document = Document()
private val accumulators: MutableMap<String, AggregationExpression> = mutableMapOf()

/**
* 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
}

/**
* Specifies the group key to a field path.
*
* @param path The field name.
*/
fun id(path: String) {
document["_id"] = "$$path"
}

/**
* Specifies the group key to a field path.
*
* @param property The property reference.
*/
fun id(property: KProperty<*>) {
id(property.toDotPath())
}

/**
* 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 <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/group/#std-label-accumulators-group">accumulator operators</a>
*/
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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}
})
Original file line number Diff line number Diff line change
@@ -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(),
)
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -303,4 +303,20 @@ internal class AggregationExpressionDslTest : FreeSpec({
)
}
}

"count" - {
"should build an expression" {
// when
val result = expression { count() }

// then
result.shouldBeJson(
"""
{
"${'$'}count": {}
}
""".trimIndent(),
)
}
}
})

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading