Skip to content

Commit

Permalink
Merge pull request #106 from UsenkoArtem/add_explain_result_to_hits
Browse files Browse the repository at this point in the history
Add explanation to hits
  • Loading branch information
anti-social authored Jul 24, 2023
2 parents c911150 + 1358677 commit 8339ab0
Show file tree
Hide file tree
Showing 7 changed files with 131 additions and 28 deletions.
26 changes: 22 additions & 4 deletions elasticmagic/api/elasticmagic.api
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,22 @@ public final class dev/evo/elasticmagic/ElasticsearchIndex {
public final fun updateByQueryAsync (Ldev/evo/elasticmagic/SearchQuery$Update;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

public final class dev/evo/elasticmagic/Explanation {
public fun <init> (FLjava/lang/String;Ljava/util/List;)V
public synthetic fun <init> (FLjava/lang/String;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/util/List;
public final fun copy (FLjava/lang/String;Ljava/util/List;)Ldev/evo/elasticmagic/Explanation;
public static synthetic fun copy$default (Ldev/evo/elasticmagic/Explanation;FLjava/lang/String;Ljava/util/List;ILjava/lang/Object;)Ldev/evo/elasticmagic/Explanation;
public fun equals (Ljava/lang/Object;)Z
public final fun getDescription ()Ljava/lang/String;
public final fun getDetails ()Ljava/util/List;
public final fun getValue ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class dev/evo/elasticmagic/MultiSearchQueryResult {
public fun <init> (Ljava/lang/Long;Ljava/util/List;)V
public final fun component1 ()Ljava/lang/Long;
Expand Down Expand Up @@ -443,11 +459,12 @@ public final class dev/evo/elasticmagic/Refresh : java/lang/Enum, dev/evo/elasti
}

public final class dev/evo/elasticmagic/SearchHit : dev/evo/elasticmagic/bulk/ActionMeta {
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;Ldev/evo/elasticmagic/Explanation;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;Ldev/evo/elasticmagic/Explanation;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component10 ()Ldev/evo/elasticmagic/doc/BaseDocSource;
public final fun component11 ()Ldev/evo/elasticmagic/SearchHit$Fields;
public final fun component12 ()Ldev/evo/elasticmagic/Explanation;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
Expand All @@ -456,9 +473,10 @@ public final class dev/evo/elasticmagic/SearchHit : dev/evo/elasticmagic/bulk/Ac
public final fun component7 ()Ljava/lang/Long;
public final fun component8 ()Ljava/lang/Float;
public final fun component9 ()Ljava/util/List;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;)Ldev/evo/elasticmagic/SearchHit;
public static synthetic fun copy$default (Ldev/evo/elasticmagic/SearchHit;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;ILjava/lang/Object;)Ldev/evo/elasticmagic/SearchHit;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;Ldev/evo/elasticmagic/Explanation;)Ldev/evo/elasticmagic/SearchHit;
public static synthetic fun copy$default (Ldev/evo/elasticmagic/SearchHit;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Long;Ljava/lang/Float;Ljava/util/List;Ldev/evo/elasticmagic/doc/BaseDocSource;Ldev/evo/elasticmagic/SearchHit$Fields;Ldev/evo/elasticmagic/Explanation;ILjava/lang/Object;)Ldev/evo/elasticmagic/SearchHit;
public fun equals (Ljava/lang/Object;)Z
public final fun getExplanation ()Ldev/evo/elasticmagic/Explanation;
public final fun getFields ()Ldev/evo/elasticmagic/SearchHit$Fields;
public fun getId ()Ljava/lang/String;
public final fun getIndex ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ data class MultiSearchQueryResult(
}
}

data class Explanation(
val value: Float,
val description: String,
val details: List<Explanation> = emptyList(),
)

data class SearchHit<S : BaseDocSource>(
val index: String,
val type: String,
Expand All @@ -53,6 +59,7 @@ data class SearchHit<S : BaseDocSource>(
val sort: List<Any?>? = null,
val source: S? = null,
val fields: Fields = Fields(emptyMap()),
val explanation: Explanation? = null,
) : ActionMeta {
class Fields(private val fields: Map<String, List<Any>>) {
/**
Expand Down Expand Up @@ -86,7 +93,7 @@ data class SearchHit<S : BaseDocSource>(
* @throws dev.evo.elasticmagic.types.ValueDeserializationException if the field value
* cannot be deserialized.
*/
operator fun <V> get(field: BoundField<V, *>): List<V>? {
operator fun <V> get(field: BoundField<V, *>): List<V> {
return this[field.getQualifiedFieldName()]
.map(field.getFieldType()::deserialize)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package dev.evo.elasticmagic.compile

import dev.evo.elasticmagic.AsyncResult
import dev.evo.elasticmagic.BulkError
import dev.evo.elasticmagic.BulkScrollFailure
import dev.evo.elasticmagic.BulkScrollRetries
import dev.evo.elasticmagic.CountResult
import dev.evo.elasticmagic.DeleteByQueryPartialResult
import dev.evo.elasticmagic.DeleteByQueryResult
import dev.evo.elasticmagic.Explanation
import dev.evo.elasticmagic.MultiSearchQueryResult
import dev.evo.elasticmagic.Params
import dev.evo.elasticmagic.PreparedSearchQuery
import dev.evo.elasticmagic.SearchHit
import dev.evo.elasticmagic.SearchQuery
import dev.evo.elasticmagic.SearchQueryResult
import dev.evo.elasticmagic.ToValue
import dev.evo.elasticmagic.UpdateByQueryPartialResult
import dev.evo.elasticmagic.UpdateByQueryResult
import dev.evo.elasticmagic.WithIndex
import dev.evo.elasticmagic.doc.BaseDocSource
Expand All @@ -29,20 +34,16 @@ import dev.evo.elasticmagic.serde.forEachObj
import dev.evo.elasticmagic.serde.toList
import dev.evo.elasticmagic.serde.toMap
import dev.evo.elasticmagic.toRequestParameters
import dev.evo.elasticmagic.transport.BulkRequest
import dev.evo.elasticmagic.transport.ApiRequest
import dev.evo.elasticmagic.transport.BulkRequest
import dev.evo.elasticmagic.transport.Method
import dev.evo.elasticmagic.transport.Parameters
import dev.evo.elasticmagic.BulkError
import dev.evo.elasticmagic.BulkScrollFailure
import dev.evo.elasticmagic.UpdateByQueryPartialResult
import dev.evo.elasticmagic.DeleteByQueryPartialResult

abstract class BaseSearchQueryCompiler(
features: ElasticsearchFeatures,
) : BaseCompiler(features) {

interface Visitable<T: Serializer.Ctx> {
interface Visitable<T : Serializer.Ctx> {
fun accept(ctx: T, compiler: BaseSearchQueryCompiler)
}

Expand Down Expand Up @@ -218,7 +219,7 @@ open class SearchQueryCompiler(
)
}

fun <S: BaseDocSource> processResult(
fun <S : BaseDocSource> processResult(
ctx: Deserializer.ObjectCtx,
preparedSearchQuery: SearchQuery.Search<S>,
): SearchQueryResult<S> {
Expand All @@ -231,7 +232,7 @@ open class SearchQueryCompiler(
}

val rawHits = rawHitsData.arrayOrNull("hits")
val hits = buildList<SearchHit<S>> {
val hits = buildList {
rawHits?.forEachObj { rawHit ->
add(processSearchHit(rawHit, preparedSearchQuery))
}
Expand Down Expand Up @@ -259,7 +260,7 @@ open class SearchQueryCompiler(
)
}

private fun <S: BaseDocSource> processSearchHit(
private fun <S : BaseDocSource> processSearchHit(
rawHit: Deserializer.ObjectCtx,
preparedSearchQuery: SearchQuery.Search<S>,
): SearchHit<S> {
Expand Down Expand Up @@ -295,8 +296,24 @@ open class SearchQueryCompiler(
sort = sort.ifEmpty { null },
source = source,
fields = fields,
explanation = rawHit.objOrNull("_explanation")?.let(::parseExplanation),
)
}

private fun parseExplanation(rawHit: Deserializer.ObjectCtx): Explanation {
val description = rawHit.string("description")
val value = rawHit.float("value")
val explanation =
rawHit.arrayOrNull("details")?.let {
buildList {
it.forEachObj { rawExplanation ->
add(parseExplanation(rawExplanation))
}
}
}

return Explanation(value, description, explanation ?: emptyList())
}
}

class CountQueryCompiler(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package dev.evo.elasticmagic

import dev.evo.elasticmagic.doc.BoundField
import dev.evo.elasticmagic.doc.RootFieldSet
import dev.evo.elasticmagic.qf.FIXTURES
import dev.evo.elasticmagic.qf.ItemDoc
import dev.evo.elasticmagic.query.match
import dev.evo.elasticmagic.types.TextType
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import kotlin.test.Test

class ExplanationTest : ElasticsearchTestBase() {
override val indexName = "explanation"

class StringField(name: String) : BoundField<String, String>(
name,
TextType,
Params(),
RootFieldSet
)

@Test
fun withoutExplanation() = runTestWithSerdes {
withFixtures(ItemDoc, FIXTURES) {
val searchQuery = SearchQuery()
val result = searchQuery.execute(index)
result.totalHits shouldBe 8
result.hits.mapNotNull { it.explanation } shouldBe emptyList()
}
}

@Test
fun withExplanationButEmptySearchQuery() = runTestWithSerdes {
withFixtures(ItemDoc, FIXTURES) {
val searchQuery = SearchQuery()
val result = searchQuery.execute(index, Params("explain" to true))
result.totalHits shouldBe 8
val explanations = result.hits.mapNotNull { it.explanation }

explanations.size shouldBe 8
explanations.map {
it.value shouldNotBe 1.0f
it.details shouldBe emptyList()
}
}
}

@Test
fun withExplanation() = runTestWithSerdes {
withFixtures(ItemDoc, FIXTURES) {
val searchQuery = SearchQuery(StringField("model").match("Galaxy Note 10"))
val result = searchQuery.execute(index, Params("explain" to true))
result.totalHits shouldBe 5
val explanations = result.hits.mapNotNull { it.explanation }

explanations.size shouldBe 5
explanations.map {
it.description shouldBe "sum of:"
it.details.size shouldNotBe 0
}

}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ import dev.evo.elasticmagic.aggs.NestedAgg
import dev.evo.elasticmagic.aggs.SingleBucketAggResult
import dev.evo.elasticmagic.aggs.TermsAgg
import dev.evo.elasticmagic.aggs.TermsAggResult
import dev.evo.elasticmagic.bulk.DocSourceAndMeta
import dev.evo.elasticmagic.bulk.withActionMeta
import dev.evo.elasticmagic.doc.BaseDocSource
import dev.evo.elasticmagic.doc.BoundField
import dev.evo.elasticmagic.doc.DocSource
import dev.evo.elasticmagic.doc.Document
import dev.evo.elasticmagic.bulk.DocSourceAndMeta
import dev.evo.elasticmagic.bulk.withActionMeta
import dev.evo.elasticmagic.doc.DynDocSource
import dev.evo.elasticmagic.doc.SubDocument
import dev.evo.elasticmagic.doc.enum
Expand All @@ -26,25 +26,22 @@ import dev.evo.elasticmagic.query.Sort
import dev.evo.elasticmagic.query.match
import dev.evo.elasticmagic.transport.ElasticsearchException
import dev.evo.elasticmagic.types.KeywordType

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.shouldBe
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.booleans.shouldBeFalse
import io.kotest.matchers.booleans.shouldBeTrue
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.floats.shouldBeGreaterThan
import io.kotest.matchers.nulls.shouldBeNull
import io.kotest.matchers.nulls.shouldNotBeNull
import io.kotest.matchers.floats.shouldBeGreaterThan
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldHaveMinLength
import io.kotest.matchers.types.shouldBeInstanceOf

import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.atStartOfDayIn
import kotlinx.datetime.toInstant

import kotlin.test.Test

enum class OrderStatus(val id: Int) : ToValue<Int> {
Expand Down Expand Up @@ -655,10 +652,10 @@ class SearchQueryTests : ElasticsearchTestBase() {
.execute(index)

searchResult.hits.size shouldBe 4
searchResult.hits.flatMap { hit -> hit.fields[dayOfWeekField]!! } shouldBe listOf(
searchResult.hits.flatMap { hit -> hit.fields[dayOfWeekField] } shouldBe listOf(
"Thursday", "Thursday", "Tuesday", "Wednesday"
)
searchResult.hits.flatMap { hit -> hit.fields[statusStrField]!! } shouldBe listOf(
searchResult.hits.flatMap { hit -> hit.fields[statusStrField] } shouldBe listOf(
"1", "1", "0", "0"
)
val daysOfWeekAgg = searchResult.agg<TermsAggResult<String>>("days_of_week")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class AttrSimpleFiltersTest : ElasticsearchTestBase() {
fun attrSimpleFilterTest() = runTestWithSerdes {
withFixtures(ItemDoc, FIXTURES) {
val searchQuery = SearchQuery()
searchQuery.execute(index).totalHits shouldBe 8
val result = searchQuery.execute(index)
result.totalHits shouldBe 8

ItemQueryFilters.apply(
searchQuery,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import dev.evo.elasticmagic.BaseTest
import dev.evo.elasticmagic.Params
import dev.evo.elasticmagic.SearchQuery
import dev.evo.elasticmagic.withIndex
import dev.evo.elasticmagic.doc.BaseDocSource

import io.kotest.matchers.types.shouldBeInstanceOf

@Suppress("UnnecessaryAbstractClass")
Expand Down Expand Up @@ -35,7 +33,7 @@ abstract class BaseCompilerTest<T: BaseCompiler>(
)

protected fun SearchQueryCompiler.compile(query: SearchQuery<*>): CompiledSearchQuery {
val compiled = this@compile.compile<BaseDocSource>(serde, query.prepareSearch().withIndex("test"))
val compiled = this@compile.compile(serde, query.prepareSearch().withIndex("test"))
return CompiledSearchQuery(
params = compiled.parameters,
body = compiled.body.shouldBeInstanceOf<TestSerializer.ObjectCtx>().toMap(),
Expand Down

0 comments on commit 8339ab0

Please sign in to comment.