From 1afdcca18b34b9165e500db90c06cbeb430b4bf7 Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 12:50:43 +0200 Subject: [PATCH 1/6] Add PrefixQuery trait and provide implementation --- .../zio/elasticsearch/ElasticQuery.scala | 32 +++++++++++++++++++ .../zio/elasticsearch/query/Queries.scala | 19 +++++++++++ 2 files changed, 51 insertions(+) diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala index 70862c54d..9d0e3d0cc 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala @@ -544,6 +544,38 @@ object ElasticQuery { final def nested(path: String, query: ElasticQuery[_]): NestedQuery[Any] = Nested(path = path, query = query, scoreMode = None, ignoreUnmapped = None, innerHitsField = None) + /** + * Constructs a type-safe instance of [[zio.elasticsearch.query.PrefixQuery]] using the specified parameters. + * [[zio.elasticsearch.query.PrefixQuery]] is used for matching documents that contain a specific prefix in a provided + * field. + * + * @param field + * the type-safe field for which query is specified for + * @param value + * the value that will be used for the query, represented by an instance of type `A` + * @tparam S + * document for which field query is executed + * @return + * an instance of [[zio.elasticsearch.query.PrefixQuery]] that represents the prefix query to be performed. + */ + final def prefix[S](field: Field[S, String], value: String): PrefixQuery[S] = + Prefix(field = field.toString, value = value, caseInsensitive = None) + + /** + * Constructs an instance of [[zio.elasticsearch.query.PrefixQuery]] using the specified parameters. + * [[zio.elasticsearch.query.PrefixQuery]] is used for matching documents that contain a specific prefix in a provided + * field. + * + * @param field + * the field for which query is specified for + * @param value + * the value that will be used for the query, represented by an instance of type `A` + * @return + * an instance of [[zio.elasticsearch.query.PrefixQuery]] that represents the prefix query to be performed. + */ + final def prefix(field: String, value: String): Prefix[Any] = + Prefix(field = field, value = value, caseInsensitive = 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 04f97356a..0d3d145d8 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala @@ -665,6 +665,25 @@ private[elasticsearch] case object Unbounded extends LowerBound with UpperBound private[elasticsearch] def toJson: Option[(String, Json)] = None } +sealed trait PrefixQuery[S] extends ElasticQuery[S] with HasCaseInsensitive[PrefixQuery[S]] + +private[elasticsearch] final case class Prefix[S]( + field: String, + value: String, + caseInsensitive: Option[Boolean] +) extends PrefixQuery[S] { self => + + override def caseInsensitive(value: Boolean): PrefixQuery[S] = + self.copy(caseInsensitive = Some(value)) + + override private[elasticsearch] def toJson(fieldPath: Option[String]): Json = { + val prefixFields = Some("value" -> value.toJson) ++ caseInsensitive.map( + "case_insensitive" -> _.toJson + ) + Obj("prefix" -> Obj(fieldPath.foldRight(field)(_ + "." + _) -> Obj(Chunk.fromIterable(prefixFields)))) + } +} + sealed trait RangeQuery[S, A, LB <: LowerBound, UB <: UpperBound] extends ElasticQuery[S] with HasBoost[RangeQuery[S, A, LB, UB]] From 6e1103b29cc3f60bf55432dfe159c736dd33e4fc Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 12:53:09 +0200 Subject: [PATCH 2/6] Provide unit and integration tests --- .../zio/elasticsearch/HttpExecutorSpec.scala | 26 +++++++++++ .../zio/elasticsearch/ElasticQuerySpec.scala | 44 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala b/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala index af29102d4..b76cb28df 100644 --- a/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala +++ b/modules/library/src/it/scala/zio/elasticsearch/HttpExecutorSpec.scala @@ -686,6 +686,32 @@ object HttpExecutorSpec extends IntegrationSpec { Executor.execute(ElasticRequest.createIndex(secondSearchIndex)), Executor.execute(ElasticRequest.deleteIndex(secondSearchIndex)).orDie ), + test("search for a document which contains a specific prefix using a prefix query") { + 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 = ElasticQuery.prefix( + field = TestDocument.stringField.keyword, + value = firstDocument.stringField.take(3) + ) + 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 which contains a term using a wildcard query") { checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) { (firstDocumentId, firstDocument, secondDocumentId, secondDocument) => diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index de3d42b87..9f65b87cf 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -2531,6 +2531,50 @@ object ElasticQuerySpec extends ZIOSpecDefault { assert(queryWithScoreMode.toJson(fieldPath = None))(equalTo(expectedWithScoreMode.toJson)) && assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson)) }, + test("prefix") { + val query = prefix(TestDocument.stringField, "test") + val queryWithCaseInsensitive = prefix(TestDocument.stringField, "test").caseInsensitiveTrue + val queryWithAllParams = prefix(TestDocument.stringField, "test").caseInsensitiveFalse + + val expected = + """ + |{ + | "prefix": { + | "stringField": { + | "value": "test" + | } + | } + |} + |""".stripMargin + + val expectedWithCaseInsensitive = + """ + |{ + | "prefix": { + | "stringField": { + | "value": "test", + | "case_insensitive": true + | } + | } + |} + |""".stripMargin + + val expectedWithAllParams = + """ + |{ + | "prefix": { + | "stringField": { + | "value": "test", + | "case_insensitive": false + | } + | } + |} + |""".stripMargin + + assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) && + assert(queryWithCaseInsensitive.toJson(fieldPath = None))(equalTo(expectedWithCaseInsensitive.toJson)) && + assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson)) + }, test("range") { val queryEmpty = range(TestDocument.intField) val queryEmptyWithBoost = range(TestDocument.intField).boost(3.14) From 381abe88fcf6633584abb739106f6d691c1ea17e Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 13:25:58 +0200 Subject: [PATCH 3/6] Fix code remarks --- .../scala/zio/elasticsearch/ElasticQuery.scala | 8 ++++---- .../scala/zio/elasticsearch/query/Queries.scala | 4 ++-- .../zio/elasticsearch/ElasticQuerySpec.scala | 16 +--------------- 3 files changed, 7 insertions(+), 21 deletions(-) diff --git a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala index 9d0e3d0cc..764a43c92 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala @@ -552,7 +552,7 @@ object ElasticQuery { * @param field * the type-safe field for which query is specified for * @param value - * the value that will be used for the query, represented by an instance of type `A` + * text value that will be used for the query * @tparam S * document for which field query is executed * @return @@ -569,7 +569,7 @@ object ElasticQuery { * @param field * the field for which query is specified for * @param value - * the value that will be used for the query, represented by an instance of type `A` + * text value that will be used for the query * @return * an instance of [[zio.elasticsearch.query.PrefixQuery]] that represents the prefix query to be performed. */ @@ -694,7 +694,7 @@ object ElasticQuery { * @param field * the type-safe field for which query is specified for * @param value - * the value that will be used for the query, represented by an instance of type `A` + * text value that will be used for the query * @tparam S * document for which field query is executed * @return @@ -711,7 +711,7 @@ object ElasticQuery { * @param field * the field for which query is specified for * @param value - * the value that will be used for the query, represented by an instance of type `A` + * text value that will be used for the query * @return * an instance of [[zio.elasticsearch.query.TermQuery]] that represents the term query to be performed. */ 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 0d3d145d8..765effef6 100644 --- a/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala +++ b/modules/library/src/main/scala/zio/elasticsearch/query/Queries.scala @@ -673,10 +673,10 @@ private[elasticsearch] final case class Prefix[S]( caseInsensitive: Option[Boolean] ) extends PrefixQuery[S] { self => - override def caseInsensitive(value: Boolean): PrefixQuery[S] = + def caseInsensitive(value: Boolean): PrefixQuery[S] = self.copy(caseInsensitive = Some(value)) - override private[elasticsearch] def toJson(fieldPath: Option[String]): Json = { + private[elasticsearch] def toJson(fieldPath: Option[String]): Json = { val prefixFields = Some("value" -> value.toJson) ++ caseInsensitive.map( "case_insensitive" -> _.toJson ) diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index 9f65b87cf..f94f8a452 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -2534,7 +2534,6 @@ object ElasticQuerySpec extends ZIOSpecDefault { test("prefix") { val query = prefix(TestDocument.stringField, "test") val queryWithCaseInsensitive = prefix(TestDocument.stringField, "test").caseInsensitiveTrue - val queryWithAllParams = prefix(TestDocument.stringField, "test").caseInsensitiveFalse val expected = """ @@ -2559,21 +2558,8 @@ object ElasticQuerySpec extends ZIOSpecDefault { |} |""".stripMargin - val expectedWithAllParams = - """ - |{ - | "prefix": { - | "stringField": { - | "value": "test", - | "case_insensitive": false - | } - | } - |} - |""".stripMargin - assert(query.toJson(fieldPath = None))(equalTo(expected.toJson)) && - assert(queryWithCaseInsensitive.toJson(fieldPath = None))(equalTo(expectedWithCaseInsensitive.toJson)) && - assert(queryWithAllParams.toJson(fieldPath = None))(equalTo(expectedWithAllParams.toJson)) + assert(queryWithCaseInsensitive.toJson(fieldPath = None))(equalTo(expectedWithCaseInsensitive.toJson)) }, test("range") { val queryEmpty = range(TestDocument.intField) From ae04900cc40f5adb41f32a98add9105372acc38d Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 14:09:45 +0200 Subject: [PATCH 4/6] Add documentation for prefix query --- docs/overview/queries/elastic_query_prefix.md | 31 +++++++++++++++++++ website/sidebars.js | 1 + 2 files changed, 32 insertions(+) create mode 100644 docs/overview/queries/elastic_query_prefix.md diff --git a/docs/overview/queries/elastic_query_prefix.md b/docs/overview/queries/elastic_query_prefix.md new file mode 100644 index 000000000..250e2806b --- /dev/null +++ b/docs/overview/queries/elastic_query_prefix.md @@ -0,0 +1,31 @@ +--- +id: elastic_query_prefix +title: "Prefix Query" +--- + +The `Prefix` query returns documents that contain a specific prefix in a provided field. + +In order to use the `Prefix` query import the following: +```scala +import zio.elasticsearch.query.PrefixQuery +import zio.elasticsearch.ElasticQuery._ +``` + +You can create a `Prefix` query using the `prefix` method this way: +```scala +val query: PrefixQuery = prefix(field = Document.name, value = "test") +``` + +You can create a [type-safe](https://lambdaworks.github.io/zio-elasticsearch/overview/overview_zio_prelude_schema) `Prefix` query using the `prefix` method this way: +```scala +val query: PrefixQuery = prefix(field = Document.name, value = "test") +``` + +If you want to change the `case_insensitive`, you can use `caseInsensitive`, `caseInsensitiveFalse` or `caseInsensitiveTrue` method: +```scala +val queryWithCaseInsensitive: PrefixQuery = prefix(field = Document.name, value = "test").caseInsensitive(true) +val queryWithCaseInsensitiveFalse: PrefixQuery = prefix(field = Document.name, value = "test").caseInsensitiveFalse +val queryWithCaseInsensitiveTrue: PrefixQuery = prefix(field = Document.name, value = "test").caseInsensitiveTrue +``` + +You can find more information about `Prefix` query [here](https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-prefix-query.html). diff --git a/website/sidebars.js b/website/sidebars.js index 64376675a..526e7611c 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -24,6 +24,7 @@ module.exports = { 'overview/queries/elastic_query_match_all', 'overview/queries/elastic_query_match_phrase', 'overview/queries/elastic_query_nested', + 'overview/queries/elastic_query_prefix', 'overview/queries/elastic_query_range', 'overview/queries/elastic_query_term', 'overview/queries/elastic_query_terms', From aedad4d56017fded5e01f65fb96fc71b455f9b52 Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 14:30:54 +0200 Subject: [PATCH 5/6] Fix code remarks --- docs/overview/queries/elastic_query_term.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/overview/queries/elastic_query_term.md b/docs/overview/queries/elastic_query_term.md index e0812188b..f511dc7a6 100644 --- a/docs/overview/queries/elastic_query_term.md +++ b/docs/overview/queries/elastic_query_term.md @@ -3,7 +3,7 @@ id: elastic_query_term title: "Term Query" --- -The `Term` query returns documents that contain an exact term in a provided field. +The `Term` query returns documents that contain an exact term in the provided field. In order to use the `Term` query import the following: ```scala From 91b9ad1e26c1a4ba448fffebed3c23e895da31f7 Mon Sep 17 00:00:00 2001 From: petarcurcin Date: Sat, 24 Jun 2023 14:46:37 +0200 Subject: [PATCH 6/6] Add constructing test --- .../zio/elasticsearch/ElasticQuerySpec.scala | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala index f94f8a452..618a4763d 100644 --- a/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala +++ b/modules/library/src/test/scala/zio/elasticsearch/ElasticQuerySpec.scala @@ -1007,6 +1007,29 @@ object ElasticQuerySpec extends ZIOSpecDefault { ) ) }, + test("prefix") { + val query = prefix("stringField", "test") + val queryTs = prefix(TestDocument.stringField, "test") + val queryWithSuffix = prefix(TestDocument.stringField.keyword, "test") + val queryWithCaseInsensitive = prefix(TestDocument.stringField, "test").caseInsensitiveTrue + + assert(query)( + equalTo(Prefix[Any](field = "stringField", value = "test", caseInsensitive = None)) + ) && + assert(queryTs)( + equalTo(Prefix[TestDocument](field = "stringField", value = "test", caseInsensitive = None)) + ) && + assert(queryWithSuffix)( + equalTo( + Prefix[TestDocument](field = "stringField.keyword", value = "test", caseInsensitive = None) + ) + ) && + assert(queryWithCaseInsensitive)( + equalTo( + Prefix[TestDocument](field = "stringField", value = "test", caseInsensitive = Some(true)) + ) + ) + }, test("range") { val query = range("testField") val queryString = range(TestDocument.stringField)