Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(dsl): Support 'hasChild' query #191

Merged
merged 5 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/overview/queries/elastic_query_has_child.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
id: elastic_query_has_child
title: "Has Child Query"
---

TBD
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@

package zio.elasticsearch

import zio.elasticsearch.ElasticPrimitive.ElasticPrimitive
import zio.elasticsearch.query._
import zio.schema.Schema

import ElasticPrimitive.ElasticPrimitive

object ElasticQuery {

/**
Expand Down Expand Up @@ -110,7 +109,36 @@ object ElasticQuery {
Bool[Any](filter = queries.toList, must = Nil, mustNot = Nil, should = Nil, boost = None, minimumShouldMatch = None)

/**
* Constructs an instance of [[zio.elasticsearch.query.HasParent]] using the specified parameters.
* Constructs an instance of [[zio.elasticsearch.query.HasChildQuery]] using the specified parameters.
*
* @param childType
* a name of the child relationship mapped for the join field
* @param query
* query you wish to run on child documents of the child `type` field
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A [[ElasticQuery]] ...

* @tparam S
* document for which field query is executed
* @return
* an instance of [[zio.elasticsearch.query.HasChildQuery]] that represents the has parent query to be performed.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..has child query..


final def hasChild[S: Schema](childType: String, query: ElasticQuery[S]): HasChildQuery[S] =
HasChild(childType = childType, query = query)

/**
* Constructs an instance of [[zio.elasticsearch.query.HasChildQuery]] using the specified parameters.
*
* @param childType
* a name of the child relationship mapped for the join field
* @param query
* query you wish to run on child documents of the child `type` field
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A [[ElasticQuery]] ...

* @return
* an instance of [[zio.elasticsearch.query.HasChildQuery]] that represents the has parent query to be performed.
*/
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

..has child query..

final def hasChild(childType: String, query: ElasticQuery[Any]): HasChildQuery[Any] =
HasChild(childType = childType, query = query)

/**
* Constructs an instance of [[zio.elasticsearch.query.HasParentQuery]] using the specified parameters.
*
* @param parentType
* a name of the parent relationship mapped for the join field
Expand All @@ -119,20 +147,20 @@ object ElasticQuery {
* @tparam S
* document for which field query is executed
* @return
* an instance of [[zio.elasticsearch.query.HasParent]] that represents the has parent query to be performed.
* an instance of [[zio.elasticsearch.query.HasParentQuery]] that represents the has parent query to be performed.
*/
final def hasParent[S: Schema](parentType: String, query: ElasticQuery[S]): HasParentQuery[S] =
HasParent(parentType = parentType, query = query)

/**
* Constructs an instance of [[zio.elasticsearch.query.HasParent]] using the specified parameters.
* Constructs an instance of [[zio.elasticsearch.query.HasParentQuery]] using the specified parameters.
*
* @param parentType
* a name of the parent relationship mapped for the join field
* @param query
* query you wish to run on parent documents of the `parent_type` field
* @return
* an instance of [[zio.elasticsearch.query.HasParent]] that represents the has parent query to be performed.
* an instance of [[zio.elasticsearch.query.HasParentQuery]] that represents the has parent query to be performed.
*/
final def hasParent(parentType: String, query: ElasticQuery[Any]): HasParentQuery[Any] =
HasParent[Any](parentType = parentType, query = query)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,75 @@ private[elasticsearch] final case class Exists[S](field: String) extends ExistsQ
Obj("exists" -> Obj("field" -> fieldPath.foldRight(field)(_ + "." + _).toJson))
}

sealed trait HasChildQuery[S]
extends ElasticQuery[S]
with HasIgnoreUnmapped[HasChildQuery[S]]
with HasInnerHits[HasChildQuery[S]]
with HasScoreMode[HasChildQuery[S]] {

/**
* Sets the `maxChildren` parameter parameter for the [[HasChildQuery]].
*
* Indicates maximum number of child documents that match the query allowed for a returned parent document If the
* parent document exceeds this limit, it is excluded from the search results.
*
* @param value
* the [[scala.Int]] value for `score` parameter
* @return
* a new instance of the [[HasChildQuery]] with the `score` value set.
*/
def maxChildren(value: Int): HasChildQuery[S]

/**
* Sets the `minChildren` parameter parameter for the [[HasChildQuery]].
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Sets the `minChildren` parameter parameter for the [[HasChildQuery]].
* Sets the `minChildren` parameter for the [[HasChildQuery]].

*
* Indicates minimum number of child documents that match the query required to match the query for a returned parent
* document. If the parent document does not meet this limit, it is excluded from the search results.
*
* @param value
* the [[scala.Int]] value for `score` parameter
* @return
* a new instance of the [[HasChildQuery]] with the `score` value set.
*/
def minChildren(value: Int): HasChildQuery[S]
}

private[elasticsearch] final case class HasChild[S](
childType: String,
query: ElasticQuery[S],
ignoreUnmapped: Option[Boolean] = None,
innerHitsField: Option[InnerHits] = None,
maxChildren: Option[Int] = None,
minChildren: Option[Int] = None,
scoreMode: Option[ScoreMode] = None
) extends HasChildQuery[S] { self =>

def ignoreUnmapped(value: Boolean): HasChildQuery[S] = self.copy(ignoreUnmapped = Some(value))

def innerHits(innerHits: InnerHits): HasChildQuery[S] = self.copy(innerHitsField = Some(innerHits))

def maxChildren(value: Int): HasChildQuery[S] = self.copy(maxChildren = Some(value))

def minChildren(value: Int): HasChildQuery[S] = self.copy(minChildren = Some(value))

def paramsToJson(fieldPath: Option[String]): Json =
Obj(
"has_child" -> Obj(
List(
Some("type" -> Str(childType)),
Some("query" -> query.paramsToJson(None)),
ignoreUnmapped.map("ignore_unmapped" -> Json.Bool(_)),
innerHitsField.map(_.toStringJsonPair),
maxChildren.map("max_children" -> Json.Num(_)),
minChildren.map("min_children" -> Json.Num(_)),
scoreMode.map(sm => "score_mode" -> Json.Str(sm.toString.toLowerCase))
).flatten: _*
)
)

def scoreMode(value: ScoreMode): HasChildQuery[S] = self.copy(scoreMode = Some(value))
}

sealed trait HasParentQuery[S]
extends ElasticQuery[S]
with HasIgnoreUnmapped[HasParentQuery[S]]
Expand Down Expand Up @@ -155,7 +224,8 @@ private[elasticsearch] final case class HasParent[S](
ignoreUnmapped: Option[Boolean] = None,
innerHitsField: Option[InnerHits] = None,
score: Option[Boolean] = None
) extends HasParentQuery[S] { self =>
) extends HasParentQuery[S] {
self =>

def ignoreUnmapped(value: Boolean): HasParentQuery[S] =
self.copy(ignoreUnmapped = Some(value))
Expand All @@ -178,15 +248,6 @@ private[elasticsearch] final case class HasParent[S](

def withScore(value: Boolean): HasParent[S] =
self.copy(score = Some(value))

/**
* Sets the inner hits configuration for the [[NestedQuery]].
*
* @param innerHits
* the configuration for inner hits
* @return
* a new instance of the [[ElasticQuery]] with the specified inner hits configuration.
*/
}

sealed trait MatchQuery[S] extends ElasticQuery[S] with HasBoost[MatchQuery[S]]
Expand Down
182 changes: 182 additions & 0 deletions modules/library/src/test/scala/zio/elasticsearch/QueryDSLSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -754,6 +754,46 @@ object QueryDSLSpec extends ZIOSpecDefault {
) && assert(hasParentQueryIgnoreUnmappedFalse)(
equalTo(HasParent[Any](parentType = "parent", query = matchAll, ignoreUnmapped = Some(false), score = None))
)
},
test("successfully create HasChild query") {
val query = hasChild("child", matchAll)
val queryWithIgnoreUnmapped = hasChild("child", matchAll).ignoreUnmappedTrue
val queryWithInnerHits = hasChild("child", matchAll).innerHits
val queryWithMaxChildren = hasChild("child", matchAll).maxChildren(5)
val queryWithMinChildren = hasChild("child", matchAll).minChildren(1)
val queryWithScoreMode = hasChild("child", matchAll).scoreMode(ScoreMode.Avg)
val fullQuery = hasChild("child", matchAll)
.scoreMode(ScoreMode.Avg)
.ignoreUnmappedTrue
.innerHits
.maxChildren(5)
.minChildren(1)

assert(query)(
equalTo(HasChild[Any](childType = "child", query = matchAll))
) && assert(queryWithIgnoreUnmapped)(
equalTo(HasChild[Any](childType = "child", query = matchAll, ignoreUnmapped = Some(true)))
) && assert(queryWithInnerHits)(
equalTo(HasChild[Any](childType = "child", query = matchAll, innerHitsField = Some(InnerHits())))
) && assert(queryWithMaxChildren)(
equalTo(HasChild[Any](childType = "child", query = matchAll, maxChildren = Some(5)))
) && assert(queryWithMinChildren)(
equalTo(HasChild[Any](childType = "child", query = matchAll, minChildren = Some(1)))
) && assert(queryWithScoreMode)(
equalTo(HasChild[Any](childType = "child", query = matchAll, scoreMode = Some(ScoreMode.Avg)))
) && assert(fullQuery)(
equalTo(
HasChild[Any](
childType = "child",
query = matchAll,
ignoreUnmapped = Some(true),
innerHitsField = Some(InnerHits()),
maxChildren = Some(5),
minChildren = Some(1),
scoreMode = Some(ScoreMode.Avg)
)
)
)
}
),
suite("encoding ElasticQuery as JSON")(
Expand Down Expand Up @@ -1726,6 +1766,148 @@ object QueryDSLSpec extends ZIOSpecDefault {

assert(bulkQuery)(equalTo(Validation.succeed(Some(expectedBody))))
},
test("successfully encode HasChild query") {
hasChild("child", matches("field", "value"))
val queryWithIgnoreUnmapped = hasChild("child", matches("field", "value")).ignoreUnmappedTrue
val queryWithInnerHits = hasChild("child", matches("field", "value")).innerHits
val queryWithMaxChildren = hasChild("child", matches("field", "value")).maxChildren(5)
val queryWithMinChildren = hasChild("child", matches("field", "value")).minChildren(1)
val queryWithScoreMode = hasChild("child", matches("field", "value")).scoreMode(ScoreMode.Avg)
val fullQuery = hasChild("child", matches("field", "value"))
.scoreMode(ScoreMode.Avg)
.ignoreUnmappedTrue
.innerHits
.maxChildren(5)
.minChildren(1)

"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedWithIgnoreUnmapped =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "ignore_unmapped": true,
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedWithInnerHits =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "inner_hits": {},
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedWithMaxChildren =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "max_children": 5,
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedWithMinChildren =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "min_children": 1,
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedWithScoreMode =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "score_mode": "avg",
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

val expectedFullQuery =
"""
|{
| "query": {
| "has_child": {
| "type": "child",
| "score_mode": "avg",
| "ignore_unmapped": true,
| "inner_hits": {},
| "max_children": 5,
| "min_children": 1,
| "query": {
| "match": {
| "field" : "value"
| }
| }
| }
| }
|}
|""".stripMargin

assert(queryWithIgnoreUnmapped.toJson)(equalTo(expectedWithIgnoreUnmapped.toJson)) &&
assert(queryWithInnerHits.toJson)(equalTo(expectedWithInnerHits.toJson)) &&
assert(queryWithMaxChildren.toJson)(equalTo(expectedWithMaxChildren.toJson)) &&
assert(queryWithMinChildren.toJson)(equalTo(expectedWithMinChildren.toJson)) &&
assert(queryWithScoreMode.toJson)(equalTo(expectedWithScoreMode.toJson)) &&
assert(fullQuery.toJson)(equalTo(expectedFullQuery.toJson))
},
test("successfully encode HasParent query") {
val query =
hasParent(parentType = "parent", query = matches("field", "value"))
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
'overview/queries/elastic_query_contains',
'overview/queries/elastic_query_exists',
'overview/queries/elastic_query_filter',
'overview/queries/elastic_query_has_child',
'overview/queries/elastic_query_has_parent',
'overview/queries/elastic_query_match_all',
'overview/queries/elastic_query_matches',
Expand Down