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 wildcard search operator dsl #52

Merged
merged 6 commits into from
Sep 30, 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 @@ -108,4 +108,12 @@ interface SearchOperator {
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/morelikethis">moreLikeThis</a>
*/
fun moreLikeThis(configuration: MoreLikeThisSearchOperatorDsl.() -> Unit)

/**
* Enables queries which use special characters in the search string that can match any character.
*
* @param configuration The configuration block for the [WildcardSearchOperatorDsl].
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/wildcard">wildcard</a>
*/
fun wildcard(configuration: WildcardSearchOperatorDsl.() -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,8 @@ class SearchOperatorDsl : SearchOperator {
override fun moreLikeThis(configuration: MoreLikeThisSearchOperatorDsl.() -> Unit) {
operators.add(MoreLikeThisSearchOperatorDsl().apply(configuration).build())
}

override fun wildcard(configuration: WildcardSearchOperatorDsl.() -> Unit) {
operators.add(WildcardSearchOperatorDsl().apply(configuration).build())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.github.inflab.spring.data.mongodb.core.aggregation.search

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

/**
* A Kotlin DSL to configure wildcard search operator using idiomatic Kotlin code.
*
* @author minwoo
* @since 1.0
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/wildcard">wildcard</a>
*/
@AggregationMarker
class WildcardSearchOperatorDsl {
private val document = Document()

/**
* Must be set to true if the query is run against an analyzed field.
*/
var allowAnalyzedField: Boolean = false
set(value) {
document["allowAnalyzedField"] = value
field = value
}

/**
* The string or strings to search for.
*
* @param query The string or strings to search for.
*/
fun query(vararg query: String) {
document["query"] = query.toList().firstOrAll()
}

/**
* The indexed field or fields to search.
* You can also specify a wildcard path to search.
* See path construction for more information.
*
* @param path The indexed field or fields to search.
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/path-construction/#std-label-ref-path">Path Construction</a>
*/
fun path(vararg path: String) {
document["path"] = path.toList().firstOrAll()
}

/**
* The indexed field or fields to search.
* You can also specify a wildcard path to search.
* See path construction for more information.
*
* @param path The indexed field or fields to search.
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/path-construction/#std-label-ref-path">Path Construction</a>
*/
fun path(vararg path: KProperty<String?>) {
document["path"] = path.map { it.toDotPath() }.firstOrAll()
}

/**
* The indexed field or fields to search.
* You can also specify a wildcard path to search.
* See path construction for more information.
*
* @param path The indexed field or fields to search.
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/path-construction/#std-label-ref-path">Path Construction</a>
*/
@JvmName("pathIterable")
fun path(vararg path: KProperty<Iterable<String?>?>) {
document["path"] = path.map { it.toDotPath() }.firstOrAll()
}

/**
* The indexed field or fields to search.
* You can also specify a wildcard path to search.
* See path construction for more information.
*
* @param configuration The configuration block for the [PathSearchOptionDsl].
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/path-construction/#std-label-ref-path">Path Construction</a>
*/
fun path(configuration: PathSearchOptionDsl<String>.() -> Unit) {
document["path"] = PathSearchOptionDsl<String>().apply(configuration).build()
}

/**
* Score to assign to matching search results.
* You can modify the default score using the following options:
*
* - boost: multiply the result score by the given number.
* - constant: replace the result score with the given number.
* - function: replace the result score using the given expression.
*
* @see <a href="https://www.mongodb.com/docs/atlas/atlas-search/scoring/#std-label-scoring-ref">Score the Documents in the Results</a>
*/
fun score(scoreConfiguration: ScoreSearchOptionDsl.() -> Unit) {
document["score"] = ScoreSearchOptionDsl().apply(scoreConfiguration).get()
}

internal fun build() = Document("wildcard", document)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package com.github.inflab.spring.data.mongodb.core.aggregation.search

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 WildcardSearchOperatorDslTest : FreeSpec({
fun wildcard(block: WildcardSearchOperatorDsl.() -> Unit) =
WildcardSearchOperatorDsl().apply(block)

"allowAnalyzedField" - {
"should build an option with given value" {
// given
val operator = wildcard {
allowAnalyzedField = true
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"allowAnalyzedField": true
}
}
""".trimIndent(),
)
}
}

"query" - {
"should build a query by string" {
// given
val operator = wildcard {
query("query")
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"query": "query"
}
}
""".trimIndent(),
)
}

"should build a query by multiple strings" {
// given
val operator = wildcard {
query("query1", "query2")
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"query": [
"query1",
"query2"
]
}
}
""".trimIndent(),
)
}
}

"path" - {
"should build a path by strings" {
// given
val operator = wildcard {
path("path1", "path2")
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"path": [
"path1",
"path2"
]
}
}
""".trimIndent(),
)
}

"should build a path by iterable property" {
// given
data class TestCollection(val path1: List<String>)
val operator = wildcard {
path(TestCollection::path1)
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"path": "path1"
}
}
""".trimIndent(),
)
}

"should build a path by multiple properties" {
// given
data class TestCollection(val path1: String, val path2: String)
val operator = wildcard {
path(TestCollection::path1, TestCollection::path2)
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"path": [
"path1",
"path2"
]
}
}
""".trimIndent(),
)
}

"should build a path by nested property" {
// given
data class Child(val path: String)
data class Parent(val child: Child)
val operator = wildcard {
path(Parent::child..Child::path)
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"path": "child.path"
}
}
""".trimIndent(),
)
}

"should build a path by option block" {
// given
data class TestCollection(val path1: String, val path2: List<String>)
val operator = wildcard {
path {
+"path0"
+TestCollection::path1
+TestCollection::path2
}
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"path": [
"path0",
"path1",
"path2"
]
}
}
""".trimIndent(),
)
}
}

"score" - {
"should build a score" {
// given
val operator = wildcard {
score {
constant(1.0)
}
}

// when
val result = operator.build()

// then
result.shouldBeJson(
"""
{
"wildcard": {
"score": {
"constant": {
"value": 1.0
}
}
}
}
""".trimIndent(),
)
}
}
})
Loading