Skip to content

Commit

Permalink
(dsl): Support matchBooleanPrefix query (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
milicns authored Aug 23, 2023
1 parent 1ac7c4e commit 59e58bd
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 6 deletions.
31 changes: 31 additions & 0 deletions docs/overview/queries/elastic_query_match_boolean_prefix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
id: elastic_query_match_boolean_prefix
title: "Match Boolean Prefix Query"
---

The `MatchBooleanPrefix` query analyzes its input and constructs a `bool` query from the terms. Each term except the last is used in a `term` query.
The last term is used in a `prefix` query.

In order to use the `MatchBooleanPrefix` query import the following:
```scala
import zio.elasticsearch.query.MatchBooleanPrefixQuery
import zio.elasticsearch.ElasticQuery._
```

You can create a `MatchBooleanPrefix` query using the `matchBooleanPrefix` method this way:
```scala
val query: MatchBooleanPrefixQuery = matchBooleanPrefix(field = "stringField", value = "test")
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `MatchBooleanPrefix` query using the `matchBooleanPrefix` method this way:
```scala
val query: MatchBooleanPrefixQuery = matchBooleanPrefix(field = Document.stringField, value = "test")
```

If you want to change the `minimum_should_match` parameter, you can use the `minimumShouldMatch` method:
```scala
val queryWithMinimumShouldMatch: MatchBooleanPrefixQuery = matchBooleanPrefix(field = Document.stringField, value = "test").minimumShouldMatch(2)
```

You can find more information about `MatchBooleanPrefix` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-match-bool-prefix-query.html).

Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,27 @@ object HttpExecutorSpec extends IntegrationSpec {
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("search for a document using a match boolean prefix query") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
document = firstDocument.copy(stringField = "test this is boolean")
_ <-
Executor.execute(ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, document))
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocument)
.refreshTrue
)
query = matchBooleanPrefix(TestDocument.stringField, "this is test bo")
res <- Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
} yield (assert(res)(Assertion.contains(document)) && assert(res)(!Assertion.contains(secondDocument)))
}
} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("search for a document using a match phrase query") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ object ElasticQuery {
* @tparam S
* document for which field query is executed
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.MatchQuery]] that represents the match query to be performed.
*/
Expand All @@ -408,13 +408,53 @@ object ElasticQuery {
* @param value
* the value to be matched, represented by an instance of type `A`
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.MatchQuery]] that represents the match query to be performed.
*/
final def matches[A: ElasticPrimitive](field: String, value: A): MatchQuery[Any] =
Match(field = field, value = value)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] using the specified
* parameters. [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] analyzes its input and constructs a
* [[zio.elasticsearch.query.BoolQuery]] from the terms. Each term except the last is used in a
* [[zio.elasticsearch.query.TermQuery]]. The last term is used in a [[zio.elasticsearch.query.PrefixQuery]] query.
*
* @param field
* the type-safe field for which query is specified for
* @param value
* the value to be matched, represented by an instance of type `A`
* @tparam S
* document for which field query is executed
* @tparam A
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] that represents the match boolean prefix query
* to be performed.
*/
final def matchBooleanPrefix[S, A: ElasticPrimitive](field: Field[S, A], value: A): MatchBooleanPrefixQuery[S] =
MatchBooleanPrefix(field = field.toString, value, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] using the specified parameters.
* [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] analyzes its input and constructs a
* [[zio.elasticsearch.query.BoolQuery]] from the terms. Each term except the last is used in a
* [[zio.elasticsearch.query.TermQuery]]. The last term is used in a [[zio.elasticsearch.query.PrefixQuery]].
*
* @param field
* the field for which query is specified for
* @param value
* the value to be matched, represented by an instance of type `A`
* @tparam A
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.MatchBooleanPrefixQuery]] that represents the match boolean prefix query
* to be performed.
*/
final def matchBooleanPrefix[A: ElasticPrimitive](field: String, value: A): MatchBooleanPrefixQuery[Any] =
MatchBooleanPrefix(field = field, value = value, minimumShouldMatch = None)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.MatchPhraseQuery]] using the specified parameters.
* [[zio.elasticsearch.query.MatchPhraseQuery]] analyzes the text and creates a phrase query out of the analyzed text.
Expand Down Expand Up @@ -781,7 +821,7 @@ object ElasticQuery {
* @param value
* text value that will be used for the query
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @tparam S
* document for which field query is executed
* @return
Expand All @@ -800,7 +840,7 @@ object ElasticQuery {
* @param value
* text value that will be used for the query
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.TermQuery]] that represents the term query to be performed.
*/
Expand All @@ -820,7 +860,7 @@ object ElasticQuery {
* @tparam S
* document for which field query is executed
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.TermsQuery]] that represents the term query to be performed.
*/
Expand All @@ -838,7 +878,7 @@ object ElasticQuery {
* @param values
* a list of terms that should be find in the provided field
* @tparam A
* the type of value to be matched. A JSON decoder must be in scope for this type
* the type of value to be matched. A JSON decoder must be provided in the scope for this type
* @return
* an instance of [[zio.elasticsearch.query.TermsQuery]] that represents the term query to be performed.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,27 @@ private[elasticsearch] final case class MatchAll(boost: Option[Double]) extends
Obj("match_all" -> Obj(Chunk.fromIterable(boost.map("boost" -> _.toJson))))
}

sealed trait MatchBooleanPrefixQuery[S] extends ElasticQuery[S] with HasMinimumShouldMatch[MatchBooleanPrefixQuery[S]]

private[elasticsearch] final case class MatchBooleanPrefix[S, A: ElasticPrimitive](
field: String,
value: A,
minimumShouldMatch: Option[Int]
) extends MatchBooleanPrefixQuery[S] { self =>

def minimumShouldMatch(value: Int): MatchBooleanPrefixQuery[S] =
self.copy(minimumShouldMatch = Some(value))

private[elasticsearch] def toJson(fieldPath: Option[String]): Json =
Obj(
"match_bool_prefix" -> Obj(
fieldPath.foldRight(field)(_ + "." + _) -> minimumShouldMatch.fold(value.toJson)(m =>
Obj("query" -> value.toJson) merge Obj("minimum_should_match" -> m.toJson)
)
)
)
}

sealed trait MatchPhraseQuery[S] extends ElasticQuery[S] with HasBoost[MatchPhraseQuery[S]]

private[elasticsearch] final case class MatchPhrase[S](field: String, value: String, boost: Option[Double])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,57 @@ object ElasticQuerySpec extends ZIOSpecDefault {
equalTo(MatchAll(boost = Some(3.14)))
)
},
test("matchBooleanPrefix") {
val queryString = matchBooleanPrefix("stringField", "test")
val queryBool = matchBooleanPrefix("booleanField", true)
val queryInt = matchBooleanPrefix("intField", 1)
val queryStringTs = matchBooleanPrefix(TestDocument.stringField, "test")
val queryBoolTs = matchBooleanPrefix(TestDocument.booleanField, true)
val queryIntTs = matchBooleanPrefix(TestDocument.intField, 1)
val queryWithSuffix = matchBooleanPrefix(TestDocument.stringField.raw, "test")
val queryWithMinimumShouldMatch = matchBooleanPrefix(TestDocument.stringField, "test").minimumShouldMatch(3)

assert(queryString)(
equalTo(MatchBooleanPrefix[Any, String](field = "stringField", value = "test", minimumShouldMatch = None))
) &&
assert(queryBool)(
equalTo(MatchBooleanPrefix[Any, Boolean](field = "booleanField", value = true, minimumShouldMatch = None))
) &&
assert(queryInt)(
equalTo(MatchBooleanPrefix[Any, Int](field = "intField", value = 1, minimumShouldMatch = None))
) &&
assert(queryStringTs)(
equalTo(
MatchBooleanPrefix[TestDocument, String](field = "stringField", value = "test", minimumShouldMatch = None)
)
) &&
assert(queryBoolTs)(
equalTo(
MatchBooleanPrefix[TestDocument, Boolean](field = "booleanField", value = true, minimumShouldMatch = None)
)
) &&
assert(queryIntTs)(
equalTo(MatchBooleanPrefix[TestDocument, Int](field = "intField", value = 1, minimumShouldMatch = None))
) &&
assert(queryWithSuffix)(
equalTo(
MatchBooleanPrefix[TestDocument, String](
field = "stringField.raw",
value = "test",
minimumShouldMatch = None
)
)
) &&
assert(queryWithMinimumShouldMatch)(
equalTo(
MatchBooleanPrefix[TestDocument, String](
field = "stringField",
value = "test",
minimumShouldMatch = Some(3)
)
)
)
},
test("matches") {
val queryString = matches("stringField", "test")
val queryBool = matches("booleanField", true)
Expand Down Expand Up @@ -2415,6 +2466,76 @@ object ElasticQuerySpec extends ZIOSpecDefault {
assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) &&
assert(queryWithBoost.toJson(fieldPath = None))(equalTo(expectedWithBoost.toJson))
},
test("matchBooleanPrefix") {
val queryString = matchBooleanPrefix("stringField", "test")
val queryBool = matchBooleanPrefix("booleanField", true)
val queryInt = matchBooleanPrefix("intField", 1)
val queryStringTs = matchBooleanPrefix(TestDocument.stringField, "test")
val queryBoolTs = matchBooleanPrefix(TestDocument.booleanField, true)
val queryIntTs = matchBooleanPrefix(TestDocument.intField, 1)
val queryWithSuffix = matchBooleanPrefix(TestDocument.stringField.raw, "test")
val queryWithMinimumShouldMatch = matchBooleanPrefix(TestDocument.stringField, "test").minimumShouldMatch(3)

val expectedString =
"""
|{
| "match_bool_prefix": {
| "stringField": "test"
| }
|}
|""".stripMargin

val expectedBool =
"""
|{
| "match_bool_prefix": {
| "booleanField": true
| }
|}
|""".stripMargin

val expectedInt =
"""
|{
| "match_bool_prefix": {
| "intField": 1
| }
|}
|""".stripMargin

val expectedWithSuffix =
"""
|{
| "match_bool_prefix": {
| "stringField.raw": "test"
| }
|}
|""".stripMargin

val expectedWithMinimumShouldMatch =
"""
|{
| "match_bool_prefix": {
| "stringField": {
| "query": "test",
| "minimum_should_match": 3
| }
| }
|}
|""".stripMargin

assert(queryString.toJson(fieldPath = None))(equalTo(expectedString.toJson)) &&
assert(queryBool.toJson(fieldPath = None))(equalTo(expectedBool.toJson)) &&
assert(queryInt.toJson(fieldPath = None))(equalTo(expectedInt.toJson)) &&
assert(queryWithMinimumShouldMatch.toJson(fieldPath = None))(
equalTo(expectedWithMinimumShouldMatch.toJson)
) &&
assert(queryStringTs.toJson(fieldPath = None))(equalTo(expectedString.toJson)) &&
assert(queryBoolTs.toJson(fieldPath = None))(equalTo(expectedBool.toJson)) &&
assert(queryIntTs.toJson(fieldPath = None))(equalTo(expectedInt.toJson)) &&
assert(queryWithSuffix.toJson(fieldPath = None))(equalTo(expectedWithSuffix.toJson)) &&
assert(queryWithMinimumShouldMatch.toJson(fieldPath = None))(equalTo(expectedWithMinimumShouldMatch.toJson))
},
test("matches") {
val query = matches("testField", true)
val queryTsInt = matches(TestDocument.intField, 39)
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = {
'overview/queries/elastic_query_has_parent',
'overview/queries/elastic_query_match',
'overview/queries/elastic_query_match_all',
'overview/queries/elastic_query_match_boolean_prefix',
'overview/queries/elastic_query_match_phrase',
'overview/queries/elastic_query_match_phrase_prefix',
'overview/queries/elastic_query_nested',
Expand Down

0 comments on commit 59e58bd

Please sign in to comment.