Skip to content

Commit

Permalink
Support minimumShouldMatch on BoolQuery (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
drmarjanovic authored Apr 25, 2023
1 parent c7bfd44 commit 06dafa3
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ import zio.elasticsearch.result.{Item, UpdateByQueryResult}
import zio.elasticsearch.script.Script
import zio.json.ast.Json.{Arr, Str}
import zio.stream.{Sink, ZSink}
import zio.test.Assertion._
import zio.test.TestAspect._
import zio.test._
import zio.test.TestAspect._
import zio.test.Assertion._

import java.time.LocalDate
import scala.util.Random
Expand Down Expand Up @@ -634,7 +634,7 @@ object HttpExecutorSpec extends IntegrationSpec {
nested(path = TestDocument.subDocumentList, query = matchAll)
res <-
Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
} yield assert(res)(Assertion.hasSameElements(List(firstDocument, secondDocument)))
} yield assert(res)(hasSameElements(List(firstDocument, secondDocument)))
}
} @@ around(
Executor.execute(
Expand All @@ -644,6 +644,58 @@ object HttpExecutorSpec extends IntegrationSpec {
)
),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("search for a document using should with satisfying minimumShouldMatch condition") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
_ <-
Executor.execute(
ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, firstDocument)
)
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocument)
.refreshTrue
)
query = should(
matches(TestDocument.stringField, firstDocument.stringField),
matches(TestDocument.intField, firstDocument.intField),
matches(TestDocument.doubleField, firstDocument.doubleField + 1)
).minimumShouldMatch(2)
res <- Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
} yield assert(res)(Assertion.contains(firstDocument))
}
} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("search for a document using should without satisfying minimumShouldMatch condition") {
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
_ <-
Executor.execute(
ElasticRequest.upsert[TestDocument](firstSearchIndex, firstDocumentId, firstDocument)
)
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocument)
.refreshTrue
)
query = should(
matches(TestDocument.stringField, firstDocument.stringField),
matches(TestDocument.intField, firstDocument.intField + 1),
matches(TestDocument.doubleField, firstDocument.doubleField + 1)
).minimumShouldMatch(2)
res <- Executor.execute(ElasticRequest.search(firstSearchIndex, query)).documentAs[TestDocument]
} yield assert(res)(isEmpty)
}
} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
)
) @@ shrinks(0),
suite("searching for documents with inner hits")(
Expand All @@ -668,7 +720,7 @@ object HttpExecutorSpec extends IntegrationSpec {
res =
items.map(_.innerHitAs[TestSubDocument]("subDocumentList")).collect { case Right(value) => value }
} yield assert(res)(
Assertion.hasSameElements(List(firstDocument.subDocumentList, secondDocument.subDocumentList))
hasSameElements(List(firstDocument.subDocumentList, secondDocument.subDocumentList))
)
}
} @@ around(
Expand Down
44 changes: 12 additions & 32 deletions modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ object ElasticQuery {
* an instance of [[zio.elasticsearch.query.WildcardQuery]] that represents the wildcard query to be performed.
*/
final def contains[S](field: Field[S, _], value: String): WildcardQuery[S] =
Wildcard(
field = field.toString,
value = s"*$value*",
boost = None,
caseInsensitive = None
)
Wildcard(field = field.toString, value = s"*$value*", boost = None, caseInsensitive = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.WildcardQuery]] using the specified parameters.
Expand Down Expand Up @@ -99,7 +94,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def filter[S: Schema](queries: ElasticQuery[S]*): BoolQuery[S] =
Bool[S](filter = queries.toList, must = Nil, mustNot = Nil, should = Nil, boost = None)
Bool[S](filter = queries.toList, must = Nil, mustNot = Nil, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the criteria using
Expand All @@ -112,7 +107,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def filter(queries: ElasticQuery[Any]*): BoolQuery[Any] =
Bool[Any](filter = queries.toList, must = Nil, mustNot = Nil, should = Nil, boost = None)
Bool[Any](filter = queries.toList, must = Nil, mustNot = Nil, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.MatchAllQuery]] used for matching all documents.
Expand Down Expand Up @@ -202,7 +197,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def must[S: Schema](queries: ElasticQuery[S]*): BoolQuery[S] =
Bool[S](filter = Nil, must = queries.toList, mustNot = Nil, should = Nil, boost = None)
Bool[S](filter = Nil, must = queries.toList, mustNot = Nil, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must satisfy the criteria using
Expand All @@ -215,7 +210,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def must(queries: ElasticQuery[Any]*): BoolQuery[Any] =
Bool[Any](filter = Nil, must = queries.toList, mustNot = Nil, should = Nil, boost = None)
Bool[Any](filter = Nil, must = queries.toList, mustNot = Nil, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must not satisfy the criteria
Expand All @@ -230,7 +225,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def mustNot[S: Schema](queries: ElasticQuery[S]*): BoolQuery[S] =
Bool[S](filter = Nil, must = Nil, mustNot = queries.toList, should = Nil, boost = None)
Bool[S](filter = Nil, must = Nil, mustNot = queries.toList, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that must not satisfy the criteria
Expand All @@ -243,7 +238,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def mustNot(queries: ElasticQuery[Any]*): BoolQuery[Any] =
Bool[Any](filter = Nil, must = Nil, mustNot = queries.toList, should = Nil, boost = None)
Bool[Any](filter = Nil, must = Nil, mustNot = queries.toList, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.NestedQuery]] using the specified parameters.
Expand Down Expand Up @@ -316,7 +311,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def should[S: Schema](queries: ElasticQuery[S]*): BoolQuery[S] =
Bool[S](filter = Nil, must = Nil, mustNot = Nil, should = queries.toList, boost = None)
Bool[S](filter = Nil, must = Nil, mustNot = Nil, should = queries.toList, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.BoolQuery]] with queries that should satisfy the criteria using
Expand All @@ -329,7 +324,7 @@ object ElasticQuery {
* satisfy the criteria.
*/
final def should(queries: ElasticQuery[Any]*): BoolQuery[Any] =
Bool[Any](filter = Nil, must = Nil, mustNot = Nil, should = queries.toList, boost = None)
Bool[Any](filter = Nil, must = Nil, mustNot = Nil, should = queries.toList, boost = None, minimumShouldMatch = None)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.WildcardQuery]] using the specified parameters.
Expand All @@ -346,12 +341,7 @@ object ElasticQuery {
* an instance of [[zio.elasticsearch.query.WildcardQuery]] that represents the wildcard query to be performed.
*/
final def startsWith[S](field: Field[S, _], value: String): WildcardQuery[S] =
Wildcard(
field = field.toString,
value = s"$value*",
boost = None,
caseInsensitive = None
)
Wildcard(field = field.toString, value = s"$value*", boost = None, caseInsensitive = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.WildcardQuery]] using the specified parameters.
Expand Down Expand Up @@ -385,12 +375,7 @@ object ElasticQuery {
* an instance of [[zio.elasticsearch.query.TermQuery]] that represents the term query to be performed.
*/
final def term[S, A: ElasticPrimitive](field: Field[S, A], value: A): TermQuery[S] =
Term(
field = field.toString,
value = value,
boost = None,
caseInsensitive = None
)
Term(field = field.toString, value = value, boost = None, caseInsensitive = None)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.query.TermQuery]] using the specified parameters.
Expand Down Expand Up @@ -424,12 +409,7 @@ object ElasticQuery {
* an instance of [[zio.elasticsearch.query.WildcardQuery]] that represents the wildcard query to be performed.
*/
final def wildcard[S](field: Field[S, _], value: String): Wildcard[S] =
Wildcard(
field = field.toString,
value = value,
boost = None,
caseInsensitive = None
)
Wildcard(field = field.toString, value = value, boost = None, caseInsensitive = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.WildcardQuery]] using the specified parameters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ import zio.schema.Schema
import scala.annotation.unused

sealed trait ElasticQuery[-S] { self =>
def paramsToJson(fieldPath: Option[String]): Json
private[elasticsearch] def paramsToJson(fieldPath: Option[String]): Json

final def toJson: Obj =
private[elasticsearch] final def toJson: Obj =
Obj("query" -> self.paramsToJson(None))
}

sealed trait BoolQuery[S] extends ElasticQuery[S] with HasBoost[BoolQuery[S]] {
sealed trait BoolQuery[S] extends ElasticQuery[S] with HasBoost[BoolQuery[S]] with HasMinimumShouldMatch[BoolQuery[S]] {
def filter[S1 <: S: Schema](queries: ElasticQuery[S1]*): BoolQuery[S1]

def filter(queries: ElasticQuery[Any]*): BoolQuery[S]
Expand All @@ -53,7 +53,8 @@ private[elasticsearch] final case class Bool[S](
must: List[ElasticQuery[S]],
mustNot: List[ElasticQuery[S]],
should: List[ElasticQuery[S]],
boost: Option[Double]
boost: Option[Double],
minimumShouldMatch: Option[Int]
) extends BoolQuery[S] { self =>
def boost(value: Double): BoolQuery[S] =
self.copy(boost = Some(value))
Expand All @@ -64,6 +65,9 @@ private[elasticsearch] final case class Bool[S](
def filter(queries: ElasticQuery[Any]*): BoolQuery[S] =
self.copy(filter = filter ++ queries)

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

def must[S1 <: S: Schema](queries: ElasticQuery[S1]*): BoolQuery[S1] =
self.copy(must = must ++ queries)

Expand All @@ -83,7 +87,8 @@ private[elasticsearch] final case class Bool[S](
if (must.nonEmpty) Some("must" -> Arr(must.map(_.paramsToJson(fieldPath)): _*)) else None,
if (mustNot.nonEmpty) Some("must_not" -> Arr(mustNot.map(_.paramsToJson(fieldPath)): _*)) else None,
if (should.nonEmpty) Some("should" -> Arr(should.map(_.paramsToJson(fieldPath)): _*)) else None,
boost.map("boost" -> Num(_))
boost.map("boost" -> Num(_)),
minimumShouldMatch.map("minimum_should_match" -> Num(_))
).collect { case Some(obj) => obj }

Obj("bool" -> Obj(boolFields: _*))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ package object query {
def innerHits(innerHits: InnerHits): Q
}

private[elasticsearch] trait HasMinimumShouldMatch[Q <: HasMinimumShouldMatch[Q]] {

/**
* Sets the `minimumShouldMatch` parameter for this [[ElasticQuery]]. The `minimumShouldMatch` value is the number
* of should clauses returned documents must match. If the [[zio.elasticsearch.query.BoolQuery]] includes at least
* one `should` clause and no `must`/`filter` clauses, the default value is 1. Otherwise, the default value is 0.
*
* @param value
* a number to set `minimumShouldMatch` parameter to
* @return
* a new instance of the [[ElasticQuery]] with the `minimumShouldMatch` value set.
*/
def minimumShouldMatch(value: Int): Q
}

private[elasticsearch] trait HasScoreMode[Q <: HasScoreMode[Q]] {

/**
Expand Down
Loading

0 comments on commit 06dafa3

Please sign in to comment.