From 8b3d2fd136997aeb80474eaf63128196d03b6a56 Mon Sep 17 00:00:00 2001 From: milicns <93861148+milicns@users.noreply.github.com> Date: Tue, 29 Aug 2023 01:34:52 -0700 Subject: [PATCH] (dsl): Support `constantScore` query (#307) --- .../queries/elastic_query_constant_score.md | 30 +++++++++ .../zio/elasticsearch/HttpExecutorSpec.scala | 26 ++++++++ .../zio/elasticsearch/ElasticQuery.scala | 30 +++++++++ .../zio/elasticsearch/query/Queries.scala | 16 +++++ .../zio/elasticsearch/ElasticQuerySpec.scala | 65 +++++++++++++++++++ website/sidebars.js | 1 + 6 files changed, 168 insertions(+) create mode 100644 docs/overview/queries/elastic_query_constant_score.md diff --git a/docs/overview/queries/elastic_query_constant_score.md b/docs/overview/queries/elastic_query_constant_score.md new file mode 100644 index 000000000..41a37ece5 --- /dev/null +++ b/docs/overview/queries/elastic_query_constant_score.md @@ -0,0 +1,30 @@ +--- +id: elastic_query_constant_score +title: "Constant Score Query" +--- + +The `ConstantScore` query wraps a filter query and returns every matching document with a relevance score equal to the boost parameter value. + +In order to use the `ConstantScore` query import the following: +```scala +import zio.elasticsearch.query.ConstantScoreQuery +import zio.elasticsearch.ElasticQuery._ +``` + +You can create a `ConstantScore` query with arbitrary query(`MatchPhrase` in this example) using the `constantScore` method in the following manner: +```scala +val query: ConstantScoreQuery = constantScore(matchPhrase(field = "name", value = "test")) +``` + +You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `ConstantScore` query with arbitrary [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) query(`MatchPhrase` in this example) using the `constantScore` method in the following manner: +```scala +val query: ConstantScoreQuery = constantScore(matchPhrase(field = Document.name, value = "test")) +``` + +If you want to change the `boost`, you can use `boost` method: +```scala +val queryWithBoost: ConstantScoreQuery = constantScore(matchPhrase(field = Document.name, value = "test")).boost(2.2) +``` + +You can find more information about `ConstantScore` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-constant-score-query.html). + diff --git a/modules/integration/src/test/scala/zio/elasticsearch/HttpExecutorSpec.scala b/modules/integration/src/test/scala/zio/elasticsearch/HttpExecutorSpec.scala index b98f0c493..072ba5667 100644 --- a/modules/integration/src/test/scala/zio/elasticsearch/HttpExecutorSpec.scala +++ b/modules/integration/src/test/scala/zio/elasticsearch/HttpExecutorSpec.scala @@ -724,6 +724,32 @@ object HttpExecutorSpec extends IntegrationSpec { ) ), suite("searching for documents")( + test("search for a document using a constant score query") { + checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) { + (firstDocumentId, firstDocument, secondDocumentId, secondDocument) => + for { + _ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll)) + document = firstDocument.copy(stringField = "this is a test") + _ <- + Executor.execute(ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, document)) + _ <- Executor.execute( + ElasticRequest + .upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocument) + .refreshTrue + ) + query = constantScore( + matchPhrase( + field = TestDocument.stringField, + value = "test" + ) + ).boost(2.1) + 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 first 2 documents using range query") { checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument, genDocumentId, genTestDocument) { (firstDocumentId, firstDocument, secondDocumentId, secondDocument, thirdDocumentId, thirdDocument) => diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala index a5376a691..c8abf8bc0 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala @@ -25,6 +25,36 @@ import zio.schema.Schema object ElasticQuery { + /** + * 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 + * relevance score equal to the boost parameter value. + * + * @param query + * query to be wrapped inside of constant score query + * @tparam S + * document for which field query is specified for. An implicit `Schema` instance must be provided in the scope + * @return + * an instance of [[zio.elasticsearch.query.ConstantScoreQuery]] that represents the constant score query with query + * that must satisfy the criteria to be performed. + */ + final def constantScore[S: Schema](query: ElasticQuery[S]): ConstantScoreQuery[S] = + ConstantScore[S](query = query, boost = None) + + /** + * Constructs an 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 + * relevance score equal to the boost parameter value. + * + * @param query + * query to be wrapped inside of constant score query + * @return + * an instance of [[zio.elasticsearch.query.ConstantScoreQuery]] that represents the constant score query with query + * that must satisfy the criteria to be performed. + */ + final def constantScore(query: ElasticQuery[Any]): ConstantScoreQuery[Any] = + ConstantScore[Any](query = query, boost = None) + /** * Constructs a type-safe instance of [[zio.elasticsearch.query.WildcardQuery]] using the specified parameters. * [[zio.elasticsearch.query.WildcardQuery]] is used for matching documents containing a value that contains the diff --git a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala index a9cc34387..648c9e7c1 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala @@ -182,6 +182,22 @@ private[elasticsearch] final case class Bool[S]( } } +sealed trait ConstantScoreQuery[S] extends ElasticQuery[S] with HasBoost[ConstantScoreQuery[S]] + +private[elasticsearch] final case class ConstantScore[S](query: ElasticQuery[S], boost: Option[Double]) + extends ConstantScoreQuery[S] { self => + + def boost(value: Double): ConstantScoreQuery[S] = + self.copy(boost = Some(value)) + + private[elasticsearch] def toJson(fieldPath: Option[String]): Json = + Obj( + "constant_score" -> (Obj("filter" -> query.toJson(fieldPath)) merge boost.fold(Obj())(b => + Obj("boost" -> b.toJson) + )) + ) +} + sealed trait ExistsQuery[S] extends ElasticQuery[S] with HasBoost[ExistsQuery[S]] private[elasticsearch] final case class Exists[S](field: String, boost: Option[Double]) extends ExistsQuery[S] { self => diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index 5b92b2a2e..53007253c 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -333,6 +333,36 @@ object ElasticQuerySpec extends ZIOSpecDefault { ) } ), + test("constantScore") { + val query = constantScore(terms("stringField", "a", "b", "c")) + val queryTs = constantScore(terms(TestDocument.stringField, "a", "b", "c")) + val queryWithBoost = constantScore(terms(TestDocument.stringField, "a", "b", "c")).boost(2.2) + + assert(query)( + equalTo( + ConstantScore[Any]( + Terms(field = "stringField", values = Chunk("a", "b", "c"), boost = None), + boost = None + ) + ) + ) && + assert(queryTs)( + equalTo( + ConstantScore[TestDocument]( + Terms(field = "stringField", values = Chunk("a", "b", "c"), boost = None), + boost = None + ) + ) + ) && + assert(queryWithBoost)( + equalTo( + ConstantScore[TestDocument]( + Terms(field = "stringField", values = Chunk("a", "b", "c"), boost = None), + boost = Some(2.2) + ) + ) + ) + }, test("contains") { val query = contains("testField", "test") val queryTs = contains(TestDocument.stringField, "test") @@ -1924,6 +1954,41 @@ object ElasticQuerySpec extends ZIOSpecDefault { assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson)) } ), + test("constantScore") { + val query = constantScore(matchPhrase("stringField", "test")) + val queryTs = constantScore(matchPhrase(TestDocument.stringField, "test")) + val queryWithBoost = constantScore(matchPhrase(TestDocument.stringField, "test")).boost(1.5) + + val expected = + """ + |{ + | "constant_score": { + | "filter": { + | "match_phrase": { + | "stringField": "test" + | } + | } + | } + |} + |""".stripMargin + val expectedWithBoost = + """ + |{ + | "constant_score": { + | "filter": { + | "match_phrase": { + | "stringField": "test" + | } + | }, + | "boost": 1.5 + | } + |} + |""".stripMargin + + assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) && + assert(queryTs.toJson(fieldPath = None))(equalTo(expected.toJson)) && + assert(queryWithBoost.toJson(fieldPath = None))(equalTo(expectedWithBoost.toJson)) + }, test("contains") { val query = contains(TestDocument.stringField, "test") val queryWithBoost = contains(TestDocument.stringField, "test").boost(3.14) diff --git a/website/sidebars.js b/website/sidebars.js index c633ae1c2..284512834 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -15,6 +15,7 @@ module.exports = { items: [ 'overview/elastic_query', 'overview/queries/elastic_query_bool', + 'overview/queries/elastic_query_constant_score', 'overview/queries/elastic_query_exists', 'overview/queries/elastic_query_function_score', 'overview/queries/elastic_query_geo_distance',