Skip to content
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
20 changes: 10 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,13 @@ val results = client.search(SQLQuery(sqlQuery))
"aggs": {
"restaurant_name": {
"terms": {
"field": "restaurant_name.keyword",
"field": "restaurant_name",
"size": 1000
},
"aggs": {
"restaurant_city": {
"terms": {
"field": "restaurant_city.keyword",
"field": "restaurant_city",
"size": 1000
},
"aggs": {
Expand All @@ -296,7 +296,7 @@ val results = client.search(SQLQuery(sqlQuery))
"aggs": {
"menu_category": {
"terms": {
"field": "menus.category.keyword",
"field": "menus.category",
"size": 1000
},
"aggs": {
Expand All @@ -307,7 +307,7 @@ val results = client.search(SQLQuery(sqlQuery))
"aggs": {
"dish_name": {
"terms": {
"field": "menus.dishes.name.keyword",
"field": "menus.dishes.name",
"size": 1000
},
"aggs": {
Expand Down Expand Up @@ -339,7 +339,7 @@ val results = client.search(SQLQuery(sqlQuery))
},
"ingredient_name": {
"terms": {
"field": "menus.dishes.ingredients.name.keyword",
"field": "menus.dishes.ingredients.name",
"size": 1000
},
"aggs": {
Expand Down Expand Up @@ -787,18 +787,18 @@ ThisBuild / resolvers ++= Seq(

// For Elasticsearch 6
// Using Jest client
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es6-jest-client" % 0.13.0
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es6-jest-client" % 0.13.1
// Or using Rest High Level client
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es6-rest-client" % 0.13.0
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es6-rest-client" % 0.13.1

// For Elasticsearch 7
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es7-rest-client" % 0.13.0
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es7-rest-client" % 0.13.1

// For Elasticsearch 8
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es8-java-client" % 0.13.0
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es8-java-client" % 0.13.1

// For Elasticsearch 9
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es9-java-client" % 0.13.0
libraryDependencies += "app.softnetwork.elastic" %% s"softclient4es9-java-client" % 0.13.1
```

### **Quick Example**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,8 @@ object ElasticAggregation {
s"${aggType}_distinct_${sourceField.replace(".", "_")}"
else {
aggType match {
case th: TopHitsAggregation =>
s"${th.topHits.sql.toLowerCase}_${sourceField.replace(".", "_")}"
case th: WindowFunction =>
s"${th.window.sql.toLowerCase}_${sourceField.replace(".", "_")}"
case _ =>
s"${aggType}_${sourceField.replace(".", "_")}"

Expand Down Expand Up @@ -145,16 +145,21 @@ object ElasticAggregation {
val _agg =
aggType match {
case COUNT =>
val field =
sourceField match {
case "*" | "_id" | "_index" | "_type" => "_id"
case _ => sourceField
}
if (distinct)
cardinalityAgg(aggName, sourceField)
cardinalityAgg(aggName, field)
else {
valueCountAgg(aggName, sourceField)
valueCountAgg(aggName, field)
}
case MIN => aggWithFieldOrScript(minAgg, (name, s) => minAgg(name, sourceField).script(s))
case MAX => aggWithFieldOrScript(maxAgg, (name, s) => maxAgg(name, sourceField).script(s))
case AVG => aggWithFieldOrScript(avgAgg, (name, s) => avgAgg(name, sourceField).script(s))
case SUM => aggWithFieldOrScript(sumAgg, (name, s) => sumAgg(name, sourceField).script(s))
case th: TopHitsAggregation =>
case th: WindowFunction =>
val limit = {
th match {
case _: LastValue => 1
Expand All @@ -167,25 +172,29 @@ object ElasticAggregation {
.fetchSource(
th.identifier.name +: th.fields
.filterNot(_.isScriptField)
.filterNot(_.sourceField == th.identifier.name)
.map(_.sourceField)
.distinct
.toArray,
Array.empty
)
.copy(
scripts = th.fields
.filter(_.isScriptField)
.groupBy(_.sourceField)
.map(_._2.head)
.map(f => f.sourceField -> Script(f.painless(None)).lang("painless"))
.toMap
)
.size(limit) sortBy th.orderBy.sorts.map(sort =>
sort.order match {
case Some(Desc) =>
th.topHits match {
th.window match {
case LAST_VALUE => FieldSort(sort.field).asc()
case _ => FieldSort(sort.field).desc()
}
case _ =>
th.topHits match {
th.window match {
case LAST_VALUE => FieldSort(sort.field).desc()
case _ => FieldSort(sort.field).asc()
}
Expand Down Expand Up @@ -271,13 +280,13 @@ object ElasticAggregation {
var agg = {
bucketsDirection.get(bucket.identifier.identifierName) match {
case Some(direction) =>
termsAgg(bucket.name, s"$currentBucketPath.keyword")
termsAgg(bucket.name, currentBucketPath)
.order(Seq(direction match {
case Asc => TermsOrder("_key", asc = true)
case _ => TermsOrder("_key", asc = false)
}))
case None =>
termsAgg(bucket.name, s"$currentBucketPath.keyword")
termsAgg(bucket.name, currentBucketPath)
}
}
bucket.size.foreach(s => agg = agg.size(s))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "Country": {
| "terms": {
| "field": "Country.keyword",
| "field": "Country",
| "exclude": ["USA"],
| "order": {
| "_key": "asc"
Expand All @@ -539,7 +539,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "City": {
| "terms": {
| "field": "City.keyword",
| "field": "City",
| "exclude": ["Berlin"],
| "order": {
| "cnt": "desc"
Expand Down Expand Up @@ -793,7 +793,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "cat": {
| "terms": {
| "field": "products.category.keyword",
| "field": "products.category",
| "size": 10
| },
| "aggs": {
Expand Down Expand Up @@ -1009,7 +1009,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "userId": {
| "terms": {
| "field": "userId.keyword"
| "field": "userId"
| },
| "aggs": {
| "lastSeen": {
Expand Down Expand Up @@ -1053,7 +1053,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "Country": {
| "terms": {
| "field": "Country.keyword",
| "field": "Country",
| "exclude": ["USA"],
| "order": {
| "_key": "asc"
Expand All @@ -1062,7 +1062,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "City": {
| "terms": {
| "field": "City.keyword",
| "field": "City",
| "exclude": ["Berlin"]
| },
| "aggs": {
Expand Down Expand Up @@ -1118,7 +1118,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "Country": {
| "terms": {
| "field": "Country.keyword",
| "field": "Country",
| "exclude": [
| "USA"
| ],
Expand All @@ -1129,7 +1129,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "City": {
| "terms": {
| "field": "City.keyword",
| "field": "City",
| "exclude": [
| "Berlin"
| ]
Expand Down Expand Up @@ -1193,7 +1193,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "identifier": {
| "terms": {
| "field": "identifier.keyword",
| "field": "identifier",
| "order": {
| "ct": "desc"
| }
Expand Down Expand Up @@ -1360,7 +1360,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "identifier": {
| "terms": {
| "field": "identifier.keyword",
| "field": "identifier",
| "order": {
| "ct": "desc"
| }
Expand Down Expand Up @@ -1517,7 +1517,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "identifier": {
| "terms": {
| "field": "identifier.keyword"
| "field": "identifier"
| },
| "aggs": {
| "max_diff": {
Expand Down Expand Up @@ -2758,7 +2758,7 @@ class SQLQuerySpec extends AnyFlatSpec with Matchers {
| "aggs": {
| "dept": {
| "terms": {
| "field": "department.keyword"
| "field": "department"
| },
| "aggs": {
| "cnt": {
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ThisBuild / organization := "app.softnetwork"

name := "softclient4es"

ThisBuild / version := "0.13.0"
ThisBuild / version := "0.13.1"

ThisBuild / scalaVersion := scala213

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,59 +130,41 @@ trait SingleValueAggregateApi
// Execute the search
search(sqlQuery)
.flatMap { response =>
// Parse the response
val parseResult = ElasticResult.fromTry(parseResponse(response))

parseResult match {
// Case 1: Parse successful - process the results
case ElasticSuccess(results) =>
val aggregationResults = results.flatMap { result =>
response.aggregations.map { case (name, aggregation) =>
// Attempt to process each aggregation
val aggregationResult = ElasticResult.attempt {
val value = findAggregation(name, result).orNull match {
case b: Boolean => BooleanValue(b)
case n: Number => NumericValue(n)
case s: String => StringValue(s)
case t: Temporal => TemporalValue(t)
case m: Map[_, Any] => ObjectValue(m.map(kv => kv._1.toString -> kv._2))
case s: Seq[_] if aggregation.multivalued =>
getAggregateValue(s, aggregation.distinct)
case _ => EmptyValue
}

SingleValueAggregateResult(name, aggregation.aggType, value)
}

// Convert failures to results with errors
aggregationResult match {
case ElasticSuccess(result) => result
case ElasticFailure(error) =>
SingleValueAggregateResult(
name,
aggregation.aggType,
EmptyValue,
error = Some(s"Failed to process aggregation: ${error.message}")
)
}
}.toSeq
val results = response.results
val aggregationResults = results.flatMap { result =>
response.aggregations.map { case (name, aggregation) =>
// Attempt to process each aggregation
val aggregationResult = ElasticResult.attempt {
val value = findAggregation(name, result).orNull match {
case b: Boolean => BooleanValue(b)
case n: Number => NumericValue(n)
case s: String => StringValue(s)
case t: Temporal => TemporalValue(t)
case m: Map[_, Any] => ObjectValue(m.map(kv => kv._1.toString -> kv._2))
case s: Seq[_] if aggregation.multivalued =>
getAggregateValue(s, aggregation.distinct)
case _ => EmptyValue
}

SingleValueAggregateResult(name, aggregation.aggType, value)
}

ElasticResult.success(aggregationResults)
// Convert failures to results with errors
aggregationResult match {
case ElasticSuccess(result) => result
case ElasticFailure(error) =>
SingleValueAggregateResult(
name,
aggregation.aggType,
EmptyValue,
error = Some(s"Failed to process aggregation: ${error.message}")
)
}
}.toSeq
}

// Case 2: Parse failed - returning empty results with errors
case ElasticFailure(error) =>
val errorResults = response.aggregations.map { case (name, aggregation) =>
SingleValueAggregateResult(
name,
aggregation.aggType,
EmptyValue,
error = Some(s"Parse error: ${error.message}")
)
}.toSeq
ElasticResult.success(aggregationResults)

ElasticResult.success(errorResults)
}
}
.fold(
// If search() fails, throw an exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ trait ElasticConversion {
m: Manifest[T],
formats: Formats
): Try[Seq[T]] = {
parseResponse(response).map { rows =>
rows.map { row =>
Try(
response.results.map { row =>
convertTo[T](row)(m, formats)
}
}
)
}

// Formatters for elasticsearch ISO 8601 date/time strings
Expand All @@ -60,15 +60,17 @@ trait ElasticConversion {
* multi-search (msearch/UNION ALL) responses
*/
def parseResponse(
response: ElasticResponse
results: String,
fieldAliases: Map[String, String],
aggregations: Map[String, ClientAggregation]
): Try[Seq[Map[String, Any]]] = {
val json = mapper.readTree(response.results)
val json = mapper.readTree(results)
// Check if it's a multi-search response (array of responses)
if (json.isArray) {
parseMultiSearchResponse(json, response.fieldAliases, response.aggregations)
parseMultiSearchResponse(json, fieldAliases, aggregations)
} else {
// Single search response
parseSingleSearchResponse(json, response.fieldAliases, response.aggregations)
parseSingleSearchResponse(json, fieldAliases, aggregations)
}
}

Expand Down
Loading
Loading