Skip to content

Commit 4cf07e7

Browse files
authored
(dsl): Support Boosting query (#364)
1 parent 0f3f151 commit 4cf07e7

File tree

6 files changed

+174
-0
lines changed

6 files changed

+174
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
id: elastic_query_boosting
3+
title: "Boosting Query"
4+
---
5+
6+
The `Boosting` query returns documents that match the query marked as `positive` while reducing the relevance score of documents that also match a query which is marked as `negative` query.
7+
8+
In order to use the `Boosting` query import the following:
9+
```scala
10+
import zio.elasticsearch.query.BoostingQuery
11+
import zio.elasticsearch.ElasticQuery.boostingQuery
12+
```
13+
14+
You can create a `Boosting` query using the `boosting` method this way:
15+
```scala
16+
val query: BoostingQuery = boosting(negativeBoost = 0.5f, negativeQuery = contains(field = "testField", value = "a"), positiveQuery = startsWith(field = "testId", value = "b"))
17+
```
18+
19+
You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `Boosting` query using the `boosting` method this way:
20+
```scala
21+
val query: BoostingQuery = boosting(negativeBoost = 0.5f, negativeQuery = contains(field = Document.stringField, value = "a"), positiveQuery = startsWith(field = Document.id, value = "b"))
22+
```
23+
24+
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).

modules/integration/src/test/scala/zio/elasticsearch/HttpExecutorSpec.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,43 @@ object HttpExecutorSpec extends IntegrationSpec {
10161016
)
10171017
),
10181018
suite("searching for documents")(
1019+
test("search for a document using a boosting query") {
1020+
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
1021+
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
1022+
for {
1023+
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
1024+
firstDocumentUpdated =
1025+
firstDocument.copy(stringField = s"this is a ${firstDocument.stringField} test", intField = 7)
1026+
secondDocumentUpdated =
1027+
secondDocument.copy(
1028+
stringField = s"this is another ${secondDocument.stringField} test",
1029+
intField = 5
1030+
)
1031+
_ <-
1032+
Executor.execute(
1033+
ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, firstDocumentUpdated)
1034+
)
1035+
_ <- Executor.execute(
1036+
ElasticRequest
1037+
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocumentUpdated)
1038+
.refreshTrue
1039+
)
1040+
query = boosting(
1041+
negativeBoost = 0.1f,
1042+
negativeQuery =
1043+
term(field = TestDocument.stringField, value = firstDocument.stringField.toLowerCase),
1044+
positiveQuery = matchPhrase(
1045+
field = TestDocument.stringField,
1046+
value = "test"
1047+
)
1048+
)
1049+
res <- Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
1050+
} yield (assert(res)(equalTo(Chunk(secondDocumentUpdated, firstDocumentUpdated))))
1051+
}
1052+
} @@ around(
1053+
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
1054+
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
1055+
),
10191056
test("search for a document using a constant score query") {
10201057
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
10211058
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>

modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,52 @@ import zio.schema.Schema
2525

2626
object ElasticQuery {
2727

28+
/**
29+
* Constructs an instance of [[zio.elasticsearch.query.BoostingQuery]] with queries that must satisfy the criteria
30+
* using the specified parameters. [[zio.elasticsearch.query.BoostingQuery]] returns documents that match the query
31+
* marked as positive while reducing the relevance score of documents that also match a query which is marked as
32+
* negative query.
33+
*
34+
* @param negativeBoost
35+
* the number between 0 and 1.0 used to decrease the relevance score of documents matching the negative query
36+
* @param negativeQuery
37+
* the query that decreases the relevance score of matching documents
38+
* @param positiveQuery
39+
* the query that must be satisfied
40+
* @tparam S
41+
* document for which field query is executed. An implicit `Schema` instance must be in scope
42+
* @return
43+
* an instance of [[zio.elasticsearch.query.BoostingQuery]] that represents the boost query to be performed.
44+
*/
45+
final def boosting[S: Schema](
46+
negativeBoost: Float,
47+
negativeQuery: ElasticQuery[S],
48+
positiveQuery: ElasticQuery[S]
49+
): BoostingQuery[S] =
50+
Boosting(negativeBoost = negativeBoost, negativeQuery = negativeQuery, positiveQuery = positiveQuery)
51+
52+
/**
53+
* Constructs an instance of [[zio.elasticsearch.query.BoostingQuery]] with queries that must satisfy the criteria
54+
* using the specified parameters. [[zio.elasticsearch.query.BoostingQuery]] returns documents that match the query
55+
* marked as positive while reducing the relevance score of documents that also match a query which is marked as
56+
* negative query.
57+
*
58+
* @param negativeBoost
59+
* the number between 0 and 1.0 used to decrease the relevance score of documents matching the negative query
60+
* @param negativeQuery
61+
* the query that decreases the relevance score of matching documents
62+
* @param positiveQuery
63+
* the query that must be satisfied
64+
* @return
65+
* an instance of [[zio.elasticsearch.query.BoostingQuery]] that represents the boost query to be performed.
66+
*/
67+
final def boosting(
68+
negativeBoost: Float,
69+
negativeQuery: ElasticQuery[Any],
70+
positiveQuery: ElasticQuery[Any]
71+
): BoostingQuery[Any] =
72+
Boosting(negativeBoost = negativeBoost, negativeQuery = negativeQuery, positiveQuery = positiveQuery)
73+
2874
/**
2975
* Constructs a type-safe instance of [[zio.elasticsearch.query.ConstantScoreQuery]] with a specified query.
3076
* [[zio.elasticsearch.query.ConstantScoreQuery]] wraps a filter query and returns every matching document with a

modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,24 @@ private[elasticsearch] final case class Bool[S](
184184
}
185185
}
186186

187+
sealed trait BoostingQuery[S] extends ElasticQuery[S]
188+
189+
private[elasticsearch] final case class Boosting[S](
190+
negativeBoost: Float,
191+
negativeQuery: ElasticQuery[S],
192+
positiveQuery: ElasticQuery[S]
193+
) extends BoostingQuery[S] { self =>
194+
195+
private[elasticsearch] def toJson(fieldPath: Option[String]): Json = {
196+
val negativeBoostJson = Obj("negative_boost" -> negativeBoost.toJson)
197+
val negativeQueryJson = Obj("negative" -> negativeQuery.toJson(fieldPath))
198+
val positiveQueryJson = Obj("positive" -> positiveQuery.toJson(fieldPath))
199+
Obj(
200+
"boosting" -> (negativeBoostJson merge negativeQueryJson merge positiveQueryJson)
201+
)
202+
}
203+
}
204+
187205
sealed trait ConstantScoreQuery[S] extends ElasticQuery[S] with HasBoost[ConstantScoreQuery[S]]
188206

189207
private[elasticsearch] final case class ConstantScore[S](query: ElasticQuery[S], boost: Option[Double])

modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,28 @@ object ElasticQuerySpec extends ZIOSpecDefault {
334334
)
335335
}
336336
),
337+
test("boosting") {
338+
val query = boosting(0.5f, exists("testField"), terms("booleanField", true, false))
339+
val queryTs = boosting(0.5f, exists(TestDocument.stringField), terms(TestDocument.booleanField, true, false))
340+
341+
assert(query)(
342+
equalTo(
343+
Boosting[Any](
344+
negativeBoost = 0.5f,
345+
negativeQuery = exists("testField"),
346+
positiveQuery = terms("booleanField", true, false)
347+
)
348+
)
349+
) && assert(queryTs)(
350+
equalTo(
351+
Boosting[TestDocument](
352+
negativeBoost = 0.5f,
353+
negativeQuery = exists(TestDocument.stringField),
354+
positiveQuery = terms(TestDocument.booleanField, true, false)
355+
)
356+
)
357+
)
358+
},
337359
test("constantScore") {
338360
val query = constantScore(terms("stringField", "a", "b", "c"))
339361
val queryTs = constantScore(terms(TestDocument.stringField, "a", "b", "c"))
@@ -2446,6 +2468,32 @@ object ElasticQuerySpec extends ZIOSpecDefault {
24462468
assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson))
24472469
}
24482470
),
2471+
test("boosting") {
2472+
val query = boosting(0.5f, exists("stringField"), terms("booleanField", true, false))
2473+
val queryTs = boosting(0.5f, exists(TestDocument.stringField), terms(TestDocument.booleanField, true, false))
2474+
2475+
val expected =
2476+
"""
2477+
|{
2478+
| "boosting": {
2479+
| "positive": {
2480+
| "terms": {
2481+
| "booleanField": [ true, false ]
2482+
| }
2483+
| },
2484+
| "negative": {
2485+
| "exists": {
2486+
| "field": "stringField"
2487+
| }
2488+
| },
2489+
| "negative_boost": 0.5
2490+
| }
2491+
|}
2492+
|""".stripMargin
2493+
2494+
assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) &&
2495+
assert(queryTs.toJson(fieldPath = None))(equalTo(expected.toJson))
2496+
},
24492497
test("constantScore") {
24502498
val query = constantScore(matchPhrase("stringField", "test"))
24512499
val queryTs = constantScore(matchPhrase(TestDocument.stringField, "test"))

website/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ module.exports = {
1515
items: [
1616
'overview/elastic_query',
1717
'overview/queries/elastic_query_bool',
18+
'overview/queries/elastic_query_boosting',
1819
'overview/queries/elastic_query_constant_score',
1920
'overview/queries/elastic_query_disjunction_max',
2021
'overview/queries/elastic_query_exists',

0 commit comments

Comments
 (0)