Skip to content

Commit

Permalink
Support Boosting query
Browse files Browse the repository at this point in the history
  • Loading branch information
vanjaftn committed Nov 14, 2023
1 parent 8f9990c commit 0afc2a5
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 0 deletions.
24 changes: 24 additions & 0 deletions docs/overview/queries/elastic_query_boost.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
id: elastic_query_boost
title: "Boosting Query"
---

The `Boosting` query returns documents that match the positive query. Among those documents, the ones that also match the negative query get their relevance score lowered by multiplying it by the negative boosting factor.

In order to use the `Boosting` query import the following:
```scala
import zio.elasticsearch.query.BoostQuery
import zio.elasticsearch.ElasticQuery.boostQuery
```

You can create a `Boosting` query using the `boost` method this way:
```scala
val query: BoostQuery = boost(negativeBoost = 0.5f, negativeQuery = contains(field = "testField", value = "a"), positiveQuery = startsWith(field = "testId", value = "b"))
```

You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `Boosting` query using the `boost` method this way:
```scala
val query: BoostQuery = boost(negativeBoost = 0.5f, negativeQuery = contains(field = Document.stringField, value = "a"), positiveQuery = startsWith(field = Document.id, value = "b"))
```

You can find more information about `Boosting` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-boosting-query.html#boosting-query-ex-request).
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,43 @@ object HttpExecutorSpec extends IntegrationSpec {
)
),
suite("searching for documents")(
test("search for a document using a constant boosting query ttt") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
firstDocumentUpdated =
firstDocument.copy(stringField = s"this is a ${firstDocument.stringField} test", intField = 7)
secondDocumentUpdated =
secondDocument.copy(
stringField = s"this is another ${secondDocument.stringField} test",
intField = 5
)
_ <-
Executor.execute(
ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, firstDocumentUpdated)
)
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocumentUpdated)
.refreshTrue
)
query = boost(
negativeBoost = 0.1f,
negativeQuery =
term(field = TestDocument.stringField, value = firstDocument.stringField.toLowerCase),
positiveQuery = matchPhrase(
field = TestDocument.stringField,
value = "test"
)
)
res <- Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
} yield (assert(res)(equalTo(Chunk(secondDocumentUpdated, firstDocumentUpdated))))
}
} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("search for a document using a constant score query") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ object ElasticPrimitive {
def toJson(value: Double): Json = Num(value)
}

implicit object ElasticFloat extends ElasticPrimitive[Float] {
def toJson(value: Float): Json = Num(value)
}

implicit object ElasticInt extends ElasticPrimitive[Int] {
def toJson(value: Int): Json = Num(value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,48 @@ import zio.schema.Schema

object ElasticQuery {

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.BoostQuery]] with queries that must satisfy the
* criteria using the specified parameters.
*
* @param negativeBoost
* the query that decreases the relevance score of matching documents.
* @param negativeQuery
* the query that decreases the relevance score of matching documents.
* @param positiveQuery
* the query that must be satisfied.
* @tparam S
* document for which field query is executed. An implicit `Schema` instance must be in scope.
* @return
* an instance of [[zio.elasticsearch.query.BoostQuery]] that represents the boost query to be performed.
*/
final def boost[S: Schema](
negativeBoost: Float,
negativeQuery: ElasticQuery[S],
positiveQuery: ElasticQuery[S]
): BoostQuery[S] =
Boost(negativeBoost = negativeBoost, negativeQuery = negativeQuery, positiveQuery = positiveQuery)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoostQuery]] with queries that must satisfy the criteria using
* the specified parameters.
*
* @param negativeBoost
* the query that decreases the relevance score of matching documents.
* @param negativeQuery
* the query that decreases the relevance score of matching documents.
* @param positiveQuery
* the query that must be satisfied.
* @return
* an instance of [[zio.elasticsearch.query.BoostQuery]] that represents the boost query to be performed.
*/
final def boost(
negativeBoost: Float,
negativeQuery: ElasticQuery[Any],
positiveQuery: ElasticQuery[Any]
): BoostQuery[Any] =
Boost(negativeBoost = negativeBoost, negativeQuery = negativeQuery, positiveQuery = positiveQuery)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.ConstantScoreQuery]] with a specified query.
* [[zio.elasticsearch.query.ConstantScoreQuery]] wraps a filter query and returns every matching document with a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,24 @@ private[elasticsearch] final case class Bool[S](
}
}

sealed trait BoostQuery[S] extends ElasticQuery[S]

private[elasticsearch] final case class Boost[S](
negativeBoost: Float,
negativeQuery: ElasticQuery[S],
positiveQuery: ElasticQuery[S]
) extends BoostQuery[S] { self =>

private[elasticsearch] def toJson(fieldPath: Option[String]): Json = {
val negativeBoostJson = Obj("negative_boost" -> negativeBoost.toJson)
val negativeQueryJson = Obj("negative" -> negativeQuery.toJson(fieldPath))
val positiveQueryJson = Obj("positive" -> positiveQuery.toJson(fieldPath))
Obj(
"boosting" -> (negativeBoostJson merge negativeQueryJson merge positiveQueryJson)
)
}
}

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

private[elasticsearch] final case class ConstantScore[S](query: ElasticQuery[S], boost: Option[Double])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,28 @@ object ElasticQuerySpec extends ZIOSpecDefault {
)
}
),
test("boost") {
val query = boost(0.5f, exists("testField"), terms("booleanField", true, false))
val queryTs = boost(0.5f, exists(TestDocument.stringField), terms(TestDocument.booleanField, true, false))

assert(query)(
equalTo(
Boost[Any](
negativeBoost = 0.5f,
negativeQuery = exists("testField"),
positiveQuery = terms("booleanField", true, false)
)
)
) && assert(queryTs)(
equalTo(
Boost[TestDocument](
negativeBoost = 0.5f,
negativeQuery = exists(TestDocument.stringField),
positiveQuery = terms(TestDocument.booleanField, true, false)
)
)
)
},
test("constantScore") {
val query = constantScore(terms("stringField", "a", "b", "c"))
val queryTs = constantScore(terms(TestDocument.stringField, "a", "b", "c"))
Expand Down Expand Up @@ -2146,6 +2168,32 @@ object ElasticQuerySpec extends ZIOSpecDefault {
assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson))
}
),
test("boost") {
val query = boost(0.5f, exists("stringField"), terms("booleanField", true, false))
val queryTs = boost(0.5f, exists(TestDocument.stringField), terms(TestDocument.booleanField, true, false))

val expected =
"""
|{
| "boosting": {
| "positive": {
| "terms": {
| "booleanField": [ true, false ]
| }
| },
| "negative": {
| "exists": {
| "field": "stringField"
| }
| },
| "negative_boost": 0.5
| }
|}
|""".stripMargin

assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) &&
assert(queryTs.toJson(fieldPath = None))(equalTo(expected.toJson))
},
test("constantScore") {
val query = constantScore(matchPhrase("stringField", "test"))
val queryTs = constantScore(matchPhrase(TestDocument.stringField, "test"))
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
items: [
'overview/elastic_query',
'overview/queries/elastic_query_bool',
'overview/queries/elastic_query_boost',
'overview/queries/elastic_query_constant_score',
'overview/queries/elastic_query_exists',
'overview/queries/elastic_query_function_score',
Expand Down

0 comments on commit 0afc2a5

Please sign in to comment.