Skip to content

Commit

Permalink
feat: implement sort stage dsl
Browse files Browse the repository at this point in the history
  • Loading branch information
jbl428 authored and inflab-int committed Sep 29, 2023
1 parent 59f07da commit b5a2721
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ class AggregationDsl {
operations += ProjectStageDsl().apply(projectConfiguration).get()
}

/**
* Configures a stage that sorts all input documents and returns them to the pipeline in sorted order.
*
* @param sortConfiguration custom configurations for the sort stage.
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/sort">$sort (aggregation)</a>
*/
fun sort(sortConfiguration: SortStageDsl.() -> Unit) {
operations += SortStageDsl().apply(sortConfiguration).get()
}

/**
* Builds the [Aggregation] using the configured [AggregationOperation]s.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.github.inflab.spring.data.mongodb.core.aggregation

import org.bson.Document
import org.springframework.data.mongodb.core.aggregation.AggregationOperation
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext

class ExtendedSortOperation : AggregationOperation {
private val document: Document = Document()

fun ascending(field: String) {
document[field] = 1
}

fun descending(field: String) {
document[field] = -1
}

fun textScore(field: String) {
document[field] = Document("\$meta", "textScore")
}

@Deprecated("Deprecated in Java")
override fun toDocument(context: AggregationOperationContext) =
Document(operator, document)

override fun getOperator(): String = "\$sort"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package com.github.inflab.spring.data.mongodb.core.aggregation

import com.github.inflab.spring.data.mongodb.core.annotation.AggregationMarker
import com.github.inflab.spring.data.mongodb.core.extension.toDotPath
import kotlin.reflect.KProperty

/**
* A Kotlin DSL to configure $sort stage using idiomatic Kotlin code.
*
* @author Jake Son
* @since 1.0
* @see <a href="https://docs.mongodb.com/manual/reference/operator/aggregation/sort">$sort (aggregation)</a>
*/
@AggregationMarker
class SortStageDsl {
private val operation = ExtendedSortOperation()

enum class Order {
Ascending,
Descending,
TextScore,
}

/**
* Sort ascending.
*/
val Ascending: Order = Order.Ascending

/**
* Sort descending.
*/
val Descending: Order = Order.Descending

/**
* Sort by the computed `textScore` metadata in descending order.
*
* @see <a href="https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/#std-label-sort-pipeline-metadata">Text Score Metadata Sort</a>
*/
val TextScore: Order = Order.TextScore

/**
* Specifies the field to sort by.
*
* @param order The order to sort by.
*/
infix fun String.by(order: Order): Boolean {
when (order) {
Order.Ascending -> operation.ascending(this)
Order.Descending -> operation.descending(this)
Order.TextScore -> operation.textScore(this)
}
return true
}

/**
* Specifies the field to sort by.
*
* @param order The order to sort by.
*/
infix fun KProperty<*>.by(order: Order): Boolean =
this.toDotPath() by order

internal fun get() = operation
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package com.github.inflab.spring.data.mongodb.core.aggregation

import com.github.inflab.spring.data.mongodb.core.mapping.rangeTo
import com.github.inflab.spring.data.mongodb.core.util.shouldBeJson
import io.kotest.core.spec.style.FreeSpec

internal class SortStageDslTest : FreeSpec({
fun sort(block: SortStageDsl.() -> Unit) =
SortStageDsl().apply(block)

"ascending" - {
"should add a field by string" {
// given
val stage = sort {
"field" by Ascending
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"field": 1
}
}
""".trimIndent(),
)
}

"should add a field by property" {
// given
data class Child(val field: String)
data class Parent(val child: Child, val parentField: String)
val stage = sort {
Parent::child..Child::field by Ascending
Parent::parentField by Ascending
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"child.field": 1,
"parentField": 1
}
}
""".trimIndent(),
)
}
}

"descending" - {
"should add a field by string" {
// given
val stage = sort {
"field" by Descending
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"field": -1
}
}
""".trimIndent(),
)
}

"should add a field by property" {
// given
data class Child(val field: String)
data class Parent(val child: Child, val parentField: String)
val stage = sort {
Parent::child..Child::field by Descending
Parent::parentField by Descending
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"child.field": -1,
"parentField": -1
}
}
""".trimIndent(),
)
}
}

"textScore" - {
"should add a field by string" {
// given
val stage = sort {
"field" by TextScore
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"field": {
"${'$'}meta": "textScore"
}
}
}
""".trimIndent(),
)
}

"should add a field by property" {
// given
data class Child(val field: String)
data class Parent(val child: Child, val parentField: String)
val stage = sort {
Parent::child..Child::field by TextScore
Parent::parentField by TextScore
}

// when
val result = stage.get()

// then
result.shouldBeJson(
"""
{
"${'$'}sort": {
"child.field": {
"${'$'}meta": "textScore"
},
"parentField": {
"${'$'}meta": "textScore"
}
}
}
""".trimIndent(),
)
}
}
})

0 comments on commit b5a2721

Please sign in to comment.