-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
298 additions
and
4 deletions.
There are no files selected for viewing
56 changes: 56 additions & 0 deletions
56
modules/library/src/main/scala/zio/elasticsearch/ElasticQuery.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package zio.elasticsearch | ||
|
||
import zio.json.ast.Json | ||
import zio.json.ast.Json.{Arr, Bool, Num, Obj, Str} | ||
|
||
sealed trait ElasticQuery { self => | ||
|
||
def asJson: Json | ||
|
||
final def asJsonBody: Json = Obj("query" -> self.asJson) | ||
|
||
} | ||
|
||
object ElasticQuery { | ||
|
||
def matches(field: String, query: String): ElasticQuery = | ||
Match(field, query) | ||
|
||
def matches(field: String, query: Boolean): ElasticQuery = | ||
Match(field, query) | ||
|
||
def matches(field: String, query: Long): ElasticQuery = | ||
Match(field, query) | ||
|
||
def boolQuery(): BoolQuery = BoolQuery.empty | ||
|
||
private[elasticsearch] final case class BoolQuery(must: List[ElasticQuery], should: List[ElasticQuery]) | ||
extends ElasticQuery { self => | ||
|
||
override def asJson: Json = | ||
Obj("bool" -> Obj("must" -> Arr(must.map(_.asJson): _*), "should" -> Arr(should.map(_.asJson): _*))) | ||
|
||
def must(queries: ElasticQuery*): BoolQuery = | ||
self.copy(must = must ++ queries) | ||
|
||
def should(queries: ElasticQuery*): BoolQuery = | ||
self.copy(should = should ++ queries) | ||
} | ||
|
||
object BoolQuery { | ||
def empty: BoolQuery = BoolQuery(Nil, Nil) | ||
} | ||
|
||
private[elasticsearch] final case class Match[A](field: String, query: A) extends ElasticQuery { | ||
override def asJson: Json = | ||
query match { | ||
case str if str.isInstanceOf[String] => | ||
Obj("match" -> Obj(field -> Str(str.asInstanceOf[String]))) | ||
case bool if bool.isInstanceOf[Boolean] => | ||
Obj("match" -> Obj(field -> Bool(bool.asInstanceOf[Boolean]))) | ||
case num if num.isInstanceOf[Long] => | ||
Obj("match" -> Obj(field -> Num(num.asInstanceOf[Long]))) | ||
} | ||
} | ||
|
||
} |
62 changes: 62 additions & 0 deletions
62
modules/library/src/main/scala/zio/elasticsearch/ElasticQueryResponse.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package zio.elasticsearch | ||
|
||
import zio.json.ast.Json | ||
import zio.json.{DeriveJsonDecoder, JsonDecoder, jsonField} | ||
|
||
private[elasticsearch] final case class ElasticQueryResponse( | ||
took: Int, | ||
@jsonField("timed_out") | ||
timedOut: Boolean, | ||
@jsonField("_shards") | ||
shards: Shards, | ||
hits: Hits | ||
) | ||
|
||
private[elasticsearch] object ElasticQueryResponse { | ||
implicit val decoder: JsonDecoder[ElasticQueryResponse] = DeriveJsonDecoder.gen[ElasticQueryResponse] | ||
} | ||
|
||
private[elasticsearch] final case class Shards( | ||
total: Int, | ||
successful: Int, | ||
skipped: Int, | ||
failed: Int | ||
) | ||
|
||
private[elasticsearch] object Shards { | ||
implicit val decoder: JsonDecoder[Shards] = DeriveJsonDecoder.gen[Shards] | ||
} | ||
|
||
private[elasticsearch] final case class Hits( | ||
total: Total, | ||
@jsonField("max_score") | ||
maxScore: Double, | ||
hits: List[Item] | ||
) | ||
|
||
private[elasticsearch] object Hits { | ||
implicit val decoder: JsonDecoder[Hits] = DeriveJsonDecoder.gen[Hits] | ||
} | ||
|
||
private[elasticsearch] final case class Total(value: Long, relation: String) | ||
|
||
private[elasticsearch] object Total { | ||
implicit val decoder: JsonDecoder[Total] = DeriveJsonDecoder.gen[Total] | ||
} | ||
|
||
private[elasticsearch] final case class Item( | ||
@jsonField("_index") | ||
index: String, | ||
@jsonField("_type") | ||
`type`: String, | ||
@jsonField("_id") | ||
id: String, | ||
@jsonField("_score") | ||
score: Double, | ||
@jsonField("_source") | ||
source: Json | ||
) | ||
|
||
private[elasticsearch] object Item { | ||
implicit val decoder: JsonDecoder[Item] = DeriveJsonDecoder.gen[Item] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
modules/library/src/test/scala/zio/elasticsearch/QueryDSLSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
package zio.elasticsearch | ||
|
||
import zio.Scope | ||
import zio.elasticsearch.ElasticQuery._ | ||
import zio.elasticsearch.utils.Utils._ | ||
import zio.test._ | ||
|
||
object QueryDSLSpec extends ZIOSpecDefault { | ||
override def spec: Spec[Environment with TestEnvironment with Scope, Any] = | ||
suite("Query DSL")( | ||
suite("Creating Elastic Query Class")( | ||
test("Successfully create Match Query using `matches` method") { | ||
val queryString = matches("day_of_week", "Monday") | ||
val queryBool = matches("day_of_week", true) | ||
val queryLong = matches("day_of_week", 1L) | ||
|
||
assert(queryString)(Assertion.equalTo(Match("day_of_week", "Monday"))) | ||
assert(queryBool)(Assertion.equalTo(Match("day_of_week", true))) | ||
assert(queryLong)(Assertion.equalTo(Match("day_of_week", 1))) | ||
}, | ||
test("Successfully create `Must` Query from two Match queries") { | ||
val query = boolQuery().must(matches("day_of_week", "Monday"), matches("customer_gender", "MALE")) | ||
|
||
assert(query)( | ||
Assertion.equalTo( | ||
BoolQuery(List(Match("day_of_week", "Monday"), Match("customer_gender", "MALE")), List.empty) | ||
) | ||
) | ||
}, | ||
test("Successfully create `Should` Query from two Match queries") { | ||
val query = boolQuery().should(matches("day_of_week", "Monday"), matches("customer_gender", "MALE")) | ||
|
||
assert(query)( | ||
Assertion.equalTo( | ||
BoolQuery(List.empty, List(Match("day_of_week", "Monday"), Match("customer_gender", "MALE"))) | ||
) | ||
) | ||
}, | ||
test("Successfully create `Must/Should` mixed Query") { | ||
val query = boolQuery() | ||
.must(matches("day_of_week", "Monday"), matches("customer_gender", "MALE")) | ||
.should(matches("customer_age", 23)) | ||
|
||
assert(query)( | ||
Assertion.equalTo( | ||
BoolQuery( | ||
List(Match("day_of_week", "Monday"), Match("customer_gender", "MALE")), | ||
List(Match("customer_age", 23)) | ||
) | ||
) | ||
) | ||
}, | ||
test("Successfully create `Should/Must` mixed Query") { | ||
val query = boolQuery() | ||
.must(matches("customer_age", 23)) | ||
.should(matches("day_of_week", "Monday"), matches("customer_gender", "MALE")) | ||
|
||
assert(query)( | ||
Assertion.equalTo( | ||
BoolQuery( | ||
List(Match("customer_age", 23)), | ||
List(Match("day_of_week", "Monday"), Match("customer_gender", "MALE")) | ||
) | ||
) | ||
) | ||
} | ||
), | ||
suite("Writing out Elastic Query as Json")( | ||
test("Properly write JSON body for Match query") { | ||
val queryBool = matches("day_of_week", true) | ||
|
||
assert(queryBool.asJsonBody)( | ||
Assertion.equalTo( | ||
"""{ | ||
"query": { | ||
"match": { | ||
"day_of_week":true | ||
} | ||
} | ||
} | ||
}""".toJson | ||
) | ||
) | ||
}, | ||
test("Properly write JSON body for must query") { | ||
val queryBool = boolQuery().must(matches("day_of_week", "Monday")) | ||
|
||
assert(queryBool.asJsonBody)( | ||
Assertion.equalTo( | ||
"""{ | ||
"query": { | ||
"bool":{ | ||
"must":[ | ||
{"match": {"day_of_week":"Monday"}} | ||
], | ||
"should":[] | ||
} | ||
} | ||
}""".toJson | ||
) | ||
) | ||
}, | ||
test("Properly write JSON body for must query") { | ||
val queryBool = boolQuery().should(matches("day_of_week", "Monday")) | ||
|
||
assert(queryBool.asJsonBody)( | ||
Assertion.equalTo( | ||
"""{ | ||
"query": { | ||
"bool":{ | ||
"must":[], | ||
"should":[ | ||
{"match": {"day_of_week":"Monday"}} | ||
] | ||
} | ||
} | ||
}""".toJson | ||
) | ||
) | ||
}, | ||
test("Properly write JSON body for mixed `AND/OR` query") { | ||
val queryBool = | ||
boolQuery() | ||
.must(matches("customer_id", 1)) | ||
.should(matches("day_of_week", "Monday")) | ||
|
||
assert(queryBool.asJsonBody)( | ||
Assertion.equalTo( | ||
"""{ | ||
"query": { | ||
"bool":{ | ||
"must":[ | ||
{"match": {"customer_id":1}} | ||
], | ||
"should":[ | ||
{"match": {"day_of_week":"Monday"}} | ||
] | ||
} | ||
} | ||
}""".toJson | ||
) | ||
) | ||
} | ||
) | ||
) | ||
} |
11 changes: 11 additions & 0 deletions
11
modules/library/src/test/scala/zio/elasticsearch/utils/Utils.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package zio.elasticsearch.utils | ||
|
||
import zio.json.DecoderOps | ||
import zio.json.ast.Json | ||
|
||
object Utils { | ||
|
||
implicit class RichString(private val text: String) extends AnyVal { | ||
def toJson: Json = text.fromJson[Json].toOption.get | ||
} | ||
} |