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

Add support for range agg #272

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/overview/aggregations/elastic_aggregation_range.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,38 @@ object HttpExecutorSpec extends IntegrationSpec {
.asMinAggregation("aggregationInt")
} yield assert(aggsRes.head.value)(equalTo(23.0))
}
} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
),
test("aggregate using range aggregation") {

// TODO

val expectedResponse = ("aggregationInt", MinAggregationResult(value = 23.0))
checkOnce(genDocumentId, genTestDocument, genDocumentId, genTestDocument) {
(firstDocumentId, firstDocument, secondDocumentId, secondDocument) =>
for {
_ <- Executor.execute(ElasticRequest.deleteByQuery(firstSearchIndex, matchAll))
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, firstDocumentId, firstDocument.copy(intField = 200))
)
_ <- Executor.execute(
ElasticRequest
.upsert[TestDocument](firstSearchIndex, secondDocumentId, secondDocument.copy(intField = 23))
.refreshTrue
)
aggregation = minAggregation(name = "aggregationInt", field = TestDocument.intField)
aggsRes <- Executor
.execute(ElasticRequest.aggregate(index = firstSearchIndex, aggregation = aggregation))
.asMinAggregation("aggregationInt")
} yield assert(aggsRes.head.value)(equalTo(23.0))
}




} @@ around(
Executor.execute(ElasticRequest.createIndex(firstSearchIndex)),
Executor.execute(ElasticRequest.deleteIndex(firstSearchIndex)).orDie
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package zio.elasticsearch

import zio.Chunk
import zio.elasticsearch.aggregation._
import zio.elasticsearch.aggregation.{Range => IRange, SingleRange => Range, _}
import zio.elasticsearch.script.Script

object ElasticAggregation {
Expand Down Expand Up @@ -178,6 +178,34 @@ object ElasticAggregation {
final def multipleAggregations: MultipleAggregations =
Multiple(aggregations = Chunk.empty)

// TODO: Add docs
final def rangeAggregation[A: Numeric](
name: String,
field: Field[_, A],
range: Range,
ranges: Range*
): RangeAggregation =
IRange(
name = name,
field = field.toString,
ranges = Chunk.fromIterable(ranges.prepended(range)),
keyed = None
)

// TODO: Add docs
final def rangeAggregation(
name: String,
field: String,
range: Range,
ranges: Range*
): RangeAggregation =
IRange(
name = name,
field = field,
ranges = Chunk.fromIterable(ranges.prepended(range)),
keyed = None
)

/**
* Constructs a type-safe instance of [[zio.elasticsearch.aggregation.SumAggregation]] using the specified parameters.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,68 @@ private[elasticsearch] final case class Multiple(aggregations: Chunk[SingleElast
aggregations.map(_.toJson).reduce(_ merge _)
}

private[elasticsearch] sealed case class SingleRange(
from: Option[Double],
to: Option[Double],
key: Option[String]
) { self =>
def from(value: Double): SingleRange = self.copy(from = Some(value))
def to(value: Double): SingleRange = self.copy(to = Some(value))
def key(value: String): SingleRange = self.copy(key = Some(value))
}

object SingleRange {

def from(value: Double): SingleRange =
SingleRange(from = Some(value), to = None, key = None)

def to(value: Double): SingleRange =
SingleRange(from = None, to = Some(value), key = None)

def apply(from: Double, to: Double): SingleRange =
SingleRange(from = Some(from), to = Some(to), key = None)

}

sealed trait RangeAggregation extends SingleElasticAggregation with WithAgg {

// TODO: Scala Doc
def keyed(value: Boolean): Range

}

private[elasticsearch] final case class Range(
name: String,
field: String,
ranges: Chunk[SingleRange],
keyed: Option[Boolean]
) extends RangeAggregation { self =>

def keyed(value: Boolean): Range =
self.copy(keyed = Some(value))

def withAgg(agg: SingleElasticAggregation): MultipleAggregations =
multipleAggregations.aggregations(self, agg)

private[elasticsearch] def toJson: Json = {
val keyedJson: Json = keyed.fold(Obj())(m => Obj("keyed" -> m.toJson))

Obj(
name -> Obj(
"range" -> (Obj(
"field" -> field.toJson,
"ranges" -> Arr(ranges.map { r =>
r.from.fold(Obj())(m => Obj("from" -> m.toJson)) merge
r.to.fold(Obj())(m => Obj("to" -> m.toJson)) merge
r.key.fold(Obj())(m => Obj("key" -> m.toJson))

})
) merge keyedJson)
)
)
}
}

sealed trait SumAggregation extends SingleElasticAggregation with HasMissing[SumAggregation] with WithAgg

private[elasticsearch] final case class Sum(name: String, field: String, missing: Option[Double])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ package object elasticsearch extends IndexNameNewtype with RoutingNewtype {
def asMinAggregation(name: String): RIO[R, Option[MinAggregationResult]] =
aggregationAs[MinAggregationResult](name)

// TODO asRangeAggregation
// TODO asKeyedRangeAggregation

/**
* Executes the [[ElasticRequest.SearchRequest]] or the [[ElasticRequest.SearchAndAggregateRequest]].
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ final case class MinAggregationResult private[elasticsearch] (value: Double) ext

final case class SumAggregationResult private[elasticsearch] (value: Double) extends AggregationResult

final case class RangeAggregationResult private[elasticsearch] (

) extends AggregationResult


final case class TermsAggregationResult private[elasticsearch] (
docErrorCount: Int,
sumOtherDocCount: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,73 @@ object ElasticAggregationSpec extends ZIOSpecDefault {
)
)
},
test("range") {
val aggregationTo = rangeAggregation("aggregation1", "testField", SingleRange.to(23.9))
val aggregationFrom = rangeAggregation("aggregation2", "testField", SingleRange.from(2))
val aggregationFromTo =
rangeAggregation("aggregation3", "testField", SingleRange.from(4).to(344.0)).keyed(false)
val aggregationKeyed = rangeAggregation("aggregation4", "testField", SingleRange.to(139)).keyed(true)
val aggregationMultiple = rangeAggregation(
"aggregation5",
"testField",
SingleRange.to(23.9),
SingleRange.from(3).key("secondKey"),
SingleRange.to(12).from(0).key("thirdKey")
).keyed(true)

assert(aggregationTo)(
equalTo(
Range(
"aggregation1",
"testField",
Chunk.from(List(SingleRange(from = None, to = Some(23.9), key = None))),
None
)
)
) && assert(aggregationFrom)(
equalTo(
Range(
"aggregation2",
"testField",
Chunk.from(List(SingleRange(from = Some(2), to = None, key = None))),
None
)
)
) && assert(aggregationFromTo)(
equalTo(
Range(
"aggregation3",
"testField",
Chunk.from(List(SingleRange(from = Some(4), to = Some(344.0), key = None))),
Some(false)
)
)
) && assert(aggregationKeyed)(
equalTo(
Range(
"aggregation4",
"testField",
Chunk.from(List(SingleRange(from = None, to = Some(139), key = None))),
Some(true)
)
)
) && assert(aggregationMultiple)(
equalTo(
Range(
"aggregation5",
"testField",
Chunk.from(
List(
SingleRange(from = None, to = Some(23.9), key = None),
SingleRange(from = Some(3), to = None, key = Some("secondKey")),
SingleRange(from = Some(0), to = Some(12), key = Some("thirdKey"))
)
),
Some(true)
)
)
)
},
test("subAggregation") {
val aggregation1 = termsAggregation(name = "first", field = TestDocument.stringField).withSubAgg(
termsAggregation(name = "second", field = TestSubDocument.stringField.raw)
Expand Down Expand Up @@ -713,6 +780,97 @@ object ElasticAggregationSpec extends ZIOSpecDefault {
assert(aggregation.toJson)(equalTo(expected.toJson)) &&
assert(aggregationWithSubAggregation.toJson)(equalTo(expectedWithSubAggregation.toJson))
},
test("range") {
val aggregationTo = rangeAggregation("aggregation1", "testField", SingleRange.to(23.9))
val aggregationFrom = rangeAggregation("aggregation2", "testField", SingleRange.from(2))
val aggregationFromTo =
rangeAggregation("aggregation3", "testField", SingleRange.from(4).to(344.0)).keyed(false)
val aggregationKeyed = rangeAggregation("aggregation4", "testField", SingleRange.to(139)).keyed(true)
val aggregationMultiple = rangeAggregation(
"aggregation5",
"testField",
SingleRange.to(23.9),
SingleRange.from(3).key("secondKey"),
SingleRange.to(12).from(0).key("thirdKey")
).keyed(true)

val expectedTo =
"""
|{
| "aggregation1": {
| "range": {
| "field": "testField",
| "ranges": [
| { "to": 23.9 }
| ]
| }
| }
|}
|""".stripMargin
val expectedFrom =
"""
|{
| "aggregation2": {
| "range": {
| "field": "testField",
| "ranges": [
| { "from": 2.0 }
| ]
| }
| }
|}
|""".stripMargin
val expectedFromTo =
"""
|{
| "aggregation3": {
| "range": {
| "field": "testField",
| "keyed": false,
| "ranges": [
| { "from": 4.0, "to": 344.0 }
| ]
| }
| }
|}
|""".stripMargin
val expectedKeyed =
"""
|{
| "aggregation4": {
| "range": {
| "field": "testField",
| "keyed": true,
| "ranges": [
| { "to": 139.0 }
| ]
| }
| }
|}
|""".stripMargin
val expectedMultiple =
"""
|{
| "aggregation5": {
| "range": {
| "field": "testField",
| "keyed": true,
| "ranges": [
| { "to": 23.9 },
| { "key": "secondKey", "from": 3.0 },
| { "key": "thirdKey", "from": 0.0, "to": 12.0 }
| ]
| }
| }
|}
|""".stripMargin

assert(aggregationTo.toJson)(equalTo(expectedTo.toJson)) &&
assert(aggregationFrom.toJson)(equalTo(expectedFrom.toJson)) &&
assert(aggregationFromTo.toJson)(equalTo(expectedFromTo.toJson)) &&
assert(aggregationKeyed.toJson)(equalTo(expectedKeyed.toJson)) &&
assert(aggregationMultiple.toJson)(equalTo(expectedMultiple.toJson))
},
test("subAggregation") {
val aggregation =
termsAggregation("first", TestDocument.stringField)
Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = {
'overview/aggregations/elastic_aggregation_cardinality',
'overview/aggregations/elastic_aggregation_max',
'overview/aggregations/elastic_aggregation_min',
'overview/aggregations/elastic_aggregation_range',
'overview/aggregations/elastic_aggregation_terms',
'overview/aggregations/elastic_aggregation_sum',
],
Expand Down