From 653706e0d6ad03fdd55c8fa3ad53485b60473c01 Mon Sep 17 00:00:00 2001 From: JuliaIlic Date: Fri, 13 Jun 2025 10:04:37 +0200 Subject: [PATCH 1/2] Added QueryStringQuery --- .../zio/elasticsearch/ElasticQuery.scala | 43 +++++++++++++++ .../zio/elasticsearch/query/Queries.scala | 52 ++++++++++++++++++- .../zio/elasticsearch/ElasticQuerySpec.scala | 7 +++ 3 files changed, 100 insertions(+), 2 deletions(-) diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala index 224889176..f5c6548de 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala @@ -899,6 +899,49 @@ object ElasticQuery { final def prefix(field: String, value: String): Prefix[Any] = Prefix(field = field, value = value, caseInsensitive = None) + /** + * Constructs a type-safe instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified + * parameters. [[zio.elasticsearch.query.QueryStringQuery]] supports query strings with simple syntax for + * searching multiple fields. + * + * @param fields + * the type-safe fields to be searched + * @param query + * the query string to search for + * @tparam S + * the document type on which the query is executed + * @return + * an instance of [[zio.elasticsearch.query.QueryStringQuery]] that represents the query to be performed. + */ + final def queryStringQuery[S: Schema](query: String, fields: Field[S, _]*): QueryStringQuery[S] = + QueryString[S]( + query = query, + fields = Chunk.fromIterable(fields.map(_.toString)), + defaultField = None, + boost = None, + minimumShouldMatch = None + ) + + /** + * Constructs an instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified parameters. + * [[zio.elasticsearch.query.QueryStringQuery]] supports query strings with simple syntax for searching multiple + * fields. + * + * @param query + * the query string to search for + * @return + * an instance of [[zio.elasticsearch.query.QueryStringQuery]] that represents the query to be performed. + */ + final def queryStringQuery(query: String): QueryStringQuery[Any] = + QueryString( + query = query, + fields = Chunk.empty, + defaultField = None, + boost = None, + minimumShouldMatch = None + ) + + /** * Constructs a type-safe unbounded instance of [[zio.elasticsearch.query.RangeQuery]] using the specified parameters. * 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 189f4d86c..87f380ffd 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala @@ -22,7 +22,7 @@ import zio.elasticsearch.Field import zio.elasticsearch.query.options._ import zio.elasticsearch.query.sort.options.HasFormat import zio.json.ast.Json -import zio.json.ast.Json.{Arr, Obj} +import zio.json.ast.Json.{Arr, Null, Obj, Str} import zio.schema.Schema sealed trait ElasticQuery[-S] { self => @@ -1016,6 +1016,53 @@ private[elasticsearch] final case class Prefix[S]( } } +sealed trait QueryStringQuery[S] extends ElasticQuery[S] + with HasFields[QueryStringQuery, S] + with HasBoost[QueryStringQuery[S]] + with HasMinimumShouldMatch[QueryStringQuery[S]] + +private[elasticsearch] final case class QueryString[S]( + query: String, + fields: Chunk[String], + defaultField: Option[String], + boost: Option[Double], + minimumShouldMatch: Option[Int] +) extends QueryStringQuery[S] { self => + + def fields(field: String, fields: String*): QueryStringQuery[S] = + copy(fields = Chunk.fromIterable(field +: fields)) + + def fields[S1 <: S: Schema](fields: Chunk[Field[S1, _]]): QueryStringQuery[S1] = + copy(fields = fields.map(_.toString)) + + def fields[S1 <: S: Schema](field: Field[S1, _], fields: Field[S1, _]*): QueryStringQuery[S1] = + self.copy(fields = Chunk.fromIterable((field +: fields).map(_.toString))) + + def boost(value: Double): QueryStringQuery[S] = + self.copy(boost = Some(value)) + + def minimumShouldMatch(value: Int): QueryStringQuery[S] = + self.copy(minimumShouldMatch = Some(value)) + + override def toJson(fieldPath: Option[String]): Json = { + + val fieldsJson = if (fields.nonEmpty) Some("fields" -> Arr(fields.map(_.toJson))) else None + Obj( + "query" -> Obj( + "query_string" -> Obj( + List( + Some("query" -> Str(query)), + defaultField.map(df => "default_field" -> Str(df)), + fieldsJson, + boost.map(b => "boost" -> Json.Num(b)), + minimumShouldMatch.map(msm => "minimum_should_match" -> Json.Num(msm)) + ).flatten: _* + ) + ) + ) + } +} + sealed trait RangeQuery[S, A, LB <: LowerBound, UB <: UpperBound] extends ElasticQuery[S] with HasBoost[RangeQuery[S, A, LB, UB]] @@ -1280,7 +1327,8 @@ private[elasticsearch] final case class Wildcard[S]( value: String, boost: Option[Double], caseInsensitive: Option[Boolean] -) extends WildcardQuery[S] { self => +) extends WildcardQuery[S] { + self => def boost(value: Double): WildcardQuery[S] = self.copy(boost = Some(value)) diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index c60bb91f5..688d22dd9 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -1514,6 +1514,13 @@ object ElasticQuerySpec extends ZIOSpecDefault { ) ) }, + test("queryStringQuery"){ + val query = range("testField") + val queryNoFields = queryStringQuery("test") + val queryWithFields = queryStringQuery("test").fields("stringField1", "stringField2") + val queryWithMinShouldMatch = queryNoFields.minimumShouldMatch(2) + + }, test("range") { val query = range("testField") val queryString = range(TestDocument.stringField) From cf30e0052ae8d301f47faa032bad09ce70bba623 Mon Sep 17 00:00:00 2001 From: JuliaIlic Date: Fri, 13 Jun 2025 16:37:38 +0200 Subject: [PATCH 2/2] Implement QueryString test --- .../zio/elasticsearch/ElasticQuery.scala | 29 ++++---- .../zio/elasticsearch/query/Queries.scala | 61 ++++++++-------- .../zio/elasticsearch/ElasticQuerySpec.scala | 69 +++++++++++++++++-- 3 files changed, 105 insertions(+), 54 deletions(-) diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala index f5c6548de..6c6194715 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala @@ -900,48 +900,47 @@ object ElasticQuery { Prefix(field = field, value = value, caseInsensitive = None) /** - * Constructs a type-safe instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified - * parameters. [[zio.elasticsearch.query.QueryStringQuery]] supports query strings with simple syntax for - * searching multiple fields. + * Constructs an instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified parameters. + * [[zio.elasticsearch.query.QueryStringQuery]] supports query strings with simple syntax for searching multiple + * fields. * - * @param fields - * the type-safe fields to be searched * @param query * the query string to search for - * @tparam S - * the document type on which the query is executed * @return * an instance of [[zio.elasticsearch.query.QueryStringQuery]] that represents the query to be performed. */ - final def queryStringQuery[S: Schema](query: String, fields: Field[S, _]*): QueryStringQuery[S] = - QueryString[S]( + final def queryStringQuery(query: String): QueryString[Any] = + QueryString( query = query, - fields = Chunk.fromIterable(fields.map(_.toString)), + fields = Chunk.empty, defaultField = None, boost = None, minimumShouldMatch = None ) /** - * Constructs an instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified parameters. + * Constructs a type-safe instance of [[zio.elasticsearch.query.QueryStringQuery]] using the specified parameters. * [[zio.elasticsearch.query.QueryStringQuery]] supports query strings with simple syntax for searching multiple * fields. * + * @param fields + * the type-safe fields to be searched * @param query * the query string to search for + * @tparam S + * the document type on which the query is executed * @return * an instance of [[zio.elasticsearch.query.QueryStringQuery]] that represents the query to be performed. */ - final def queryStringQuery(query: String): QueryStringQuery[Any] = - QueryString( + final def queryStringQuery[S: Schema](query: String, fields: Field[S, _]*): QueryStringQuery[S] = + QueryString[S]( query = query, - fields = Chunk.empty, + fields = Chunk.fromIterable(fields.map(_.toString)), defaultField = None, boost = None, minimumShouldMatch = None ) - /** * Constructs a type-safe unbounded instance of [[zio.elasticsearch.query.RangeQuery]] using the specified parameters. * 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 87f380ffd..c76688461 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala @@ -22,7 +22,7 @@ import zio.elasticsearch.Field import zio.elasticsearch.query.options._ import zio.elasticsearch.query.sort.options.HasFormat import zio.json.ast.Json -import zio.json.ast.Json.{Arr, Null, Obj, Str} +import zio.json.ast.Json.{Arr, Obj, Str} import zio.schema.Schema sealed trait ElasticQuery[-S] { self => @@ -1016,51 +1016,47 @@ private[elasticsearch] final case class Prefix[S]( } } -sealed trait QueryStringQuery[S] extends ElasticQuery[S] - with HasFields[QueryStringQuery, S] - with HasBoost[QueryStringQuery[S]] - with HasMinimumShouldMatch[QueryStringQuery[S]] +sealed trait QueryStringQuery[S] + extends ElasticQuery[S] + with HasBoost[QueryStringQuery[S]] + with HasMinimumShouldMatch[QueryStringQuery[S]] private[elasticsearch] final case class QueryString[S]( - query: String, - fields: Chunk[String], defaultField: Option[String], + fields: Chunk[String], + query: String, boost: Option[Double], minimumShouldMatch: Option[Int] ) extends QueryStringQuery[S] { self => - def fields(field: String, fields: String*): QueryStringQuery[S] = - copy(fields = Chunk.fromIterable(field +: fields)) - - def fields[S1 <: S: Schema](fields: Chunk[Field[S1, _]]): QueryStringQuery[S1] = - copy(fields = fields.map(_.toString)) - - def fields[S1 <: S: Schema](field: Field[S1, _], fields: Field[S1, _]*): QueryStringQuery[S1] = - self.copy(fields = Chunk.fromIterable((field +: fields).map(_.toString))) - def boost(value: Double): QueryStringQuery[S] = self.copy(boost = Some(value)) + def fields(field: String, fields: String*): QueryStringQuery[S] = + self.copy(fields = Chunk.fromIterable(field +: fields)) + + def fields(fields: Chunk[String]): QueryStringQuery[S] = + self.copy(fields = fields) + def minimumShouldMatch(value: Int): QueryStringQuery[S] = self.copy(minimumShouldMatch = Some(value)) - override def toJson(fieldPath: Option[String]): Json = { + private[elasticsearch] def toJson(fieldPath: Option[String]): Json = { + val fieldsJson = + if (fields.nonEmpty) Some("fields" -> Arr(fields.map(Str(_)))) + else None - val fieldsJson = if (fields.nonEmpty) Some("fields" -> Arr(fields.map(_.toJson))) else None - Obj( - "query" -> Obj( - "query_string" -> Obj( - List( - Some("query" -> Str(query)), - defaultField.map(df => "default_field" -> Str(df)), - fieldsJson, - boost.map(b => "boost" -> Json.Num(b)), - minimumShouldMatch.map(msm => "minimum_should_match" -> Json.Num(msm)) - ).flatten: _* - ) - ) - ) + val params = Chunk( + Some("query" -> Str(query)), + defaultField.map("default_field" -> Str(_)), + fieldsJson, + boost.map("boost" -> Json.Num(_)), + minimumShouldMatch.map("minimum_should_match" -> Json.Num(_)) + ).flatten + + Obj("query" -> Obj("query_string" -> Obj(params))) } + } sealed trait RangeQuery[S, A, LB <: LowerBound, UB <: UpperBound] @@ -1327,8 +1323,7 @@ private[elasticsearch] final case class Wildcard[S]( value: String, boost: Option[Double], caseInsensitive: Option[Boolean] -) extends WildcardQuery[S] { - self => +) extends WildcardQuery[S] { self => def boost(value: Double): WildcardQuery[S] = self.copy(boost = Some(value)) diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index 688d22dd9..07f57b34d 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -1514,12 +1514,69 @@ object ElasticQuerySpec extends ZIOSpecDefault { ) ) }, - test("queryStringQuery"){ - val query = range("testField") - val queryNoFields = queryStringQuery("test") - val queryWithFields = queryStringQuery("test").fields("stringField1", "stringField2") - val queryWithMinShouldMatch = queryNoFields.minimumShouldMatch(2) - + test("queryStringQuery") { + val queryNoFields = queryStringQuery("(new york city) OR (big apple)") + val queryWithFields = queryStringQuery("(new york city) OR (big apple)") + .fields(Chunk("title", "description")) + val queryWithTypedFields = queryStringQuery("(new york city) OR (big apple)") + .fields(Chunk(TestDocument.stringField.toString)) + val queryWithMinShouldMatch = queryNoFields.minimumShouldMatch(1) + val queryAllParams = queryWithFields.minimumShouldMatch(1).boost(2.0) + assert(queryNoFields)( + equalTo( + QueryString[Any]( + query = "(new york city) OR (big apple)", + fields = Chunk.empty, + defaultField = None, + boost = None, + minimumShouldMatch = None + ) + ) + ) && + assert(queryWithFields)( + equalTo( + QueryString[Any]( + query = "(new york city) OR (big apple)", + fields = Chunk("title", "description"), + defaultField = None, + boost = None, + minimumShouldMatch = None + ) + ) + ) && + assert(queryWithTypedFields)( + equalTo( + QueryString[Any]( + query = "(new york city) OR (big apple)", + fields = Chunk("string"), + defaultField = None, + boost = None, + minimumShouldMatch = None + ) + ) + ) && + assert(queryWithMinShouldMatch)( + equalTo( + QueryString[Any]( + query = "(new york city) OR (big apple)", + fields = Chunk.empty, + defaultField = None, + boost = None, + minimumShouldMatch = Some(1) + ) + ) + ) && + assert(queryAllParams)( + equalTo( + QueryString[Any]( + query = "(new york city) OR (big apple)", + fields = Chunk("title", "description"), + defaultField = None, + boost = Some(2.0), + minimumShouldMatch = Some(1) + ) + ) + ) }, test("range") { val query = range("testField")