GithubHelp home page GithubHelp logo

jillesvangurp / kt-search Goto Github PK

View Code? Open in Web Editor NEW
90.0 4.0 22.0 7.58 MB

Multi platform kotlin client for Elasticsearch & Opensearch with easily extendable Kotlin DSLs for queries, mappings, bulk, and more.

License: MIT License

Kotlin 98.58% Shell 0.15% Jupyter Notebook 1.26%
client-library domain-specific-languages elasticsearch kotlin kotlin-multiplatform kotlinx-coroutines kotlinx-serialization ktor-client opensearch

kt-search's Introduction

KT Search Client

matrix-test-and-deploy-docs

Kt-search is a Kotlin Multi Platform library to search across the Opensearch and Elasticsearch ecosystem on any platform that kotlin can compile to. It provides Kotlin DSLs for querying, defining mappings, bulk indexing, index templates, index life cycle management, index aliases, and much more. The key goal for this library is to provide a best in class developer experience for using Elasticsearch and Opensearch.

Why Kt-search?

If you develop software in Kotlin and would like to use Opensearch or Elasticsearch, you have a few choices to make. There are multiple clients to choose from and not all of them work for each version. And then there is Kotlin multi platform to consider as well. Maybe you are running spring boot on the jvm. Or maybe you are using ktor compiled to native or wasm and using that to run lambda functions.

Kt-search has you covered for all of those. The official Elastic or Opensearch clients are Java clients. You can use them from Kotlin but only on the JVM. And they are not source compatible with each other. The Opensearch client is based on a fork of the old Java client which after the fork was deprecated. On top of that, it uses opensearch specific package names.

Kt-search solves a few important problems here:

  • It's Kotlin! You don't have to deal with all the Java idiomatic stuff that comes with the three Java libraries. You can write pure Kotlin code, use co-routines, and use Kotlin DSLs for everything. Simpler code, easier to debug, etc.
  • It's a multiplatform library. We use it on the jvm and in the browser (javascript). Wasm support is coming soon and there is also native and mobile support. So, your Kotlin code should be extremely portable. So, whether you are doing backend development, doing lambda functions, command line tools, mobile apps, or web apps, you can embed kt-search in each of those.
  • It doesn't force you to choose between Elasticsearch or Opensearch. Some features are specific to those products and will only work for those platforms but most of the baseline functionality is exactly the same for both.
  • It's future proof. Everything is extensible (DSLs) and modular. Even supporting custom plugins that add new features is pretty easy with the json-dsl library that is part of kt-search.

License

This project is licensed under the MIT license.

Learn more

  • Manual - this is generated from the docs module. Just like this README.md file. The manual covers most of the extensive feature set of this library. Please provide feedback via the issue tracker if something is not clear to you. Or create a pull request to improve the manual.
  • API Documentation. Dokka documentation. You can browse it, or access this in your IDE.
  • Release Notes.
  • You can also learn a lot by looking at the integration tests in the search-client module.
  • There's a full stack Kotlin demo project that we built to show off this library and a few other things.
  • The code sample below should help you figure out the basics.

Use cases

Integrate advanced search capabilities in your Kotlin applications. Whether you want to build a web based dashboard, an advanced ETL pipeline or simply expose a search endpoint as a microservice, this library has you covered.

  • Add search functionality to your server applications. Kt-search works great with Spring Boot, Ktor, Quarkus, and other popular JVM based servers. Simply create your client as a singleton object and inject it wherever you need search.
  • Build complicated ETL functionality using the Bulk indexing DSL.
  • Use Kt-search in a Kotlin-js based web application to create dashboards, or web applications that don't need a separate server. See our Full Stack at FORMATION demo project for an example.
  • For dashboards and advanced querying, aggregation support is key and kt-search provides great support for that and makes it really easy to deal with complex nested aggregations.
  • Use Kotlin Scripting to operate and introspect your cluster. See the companion project kt-search-kts for more on this as well as the scripting section in the Manual. The companion library combines kt-search with kotlinx-cli for command line argument parsing and provides some example scripts; all with the minimum of boiler plate.
  • Use kt-search from a Jupyter Notebook with the Kotlin kernel. See the jupyter-example directory for an example and check the Manual for instructions.

The goal for kt-search is to be the most convenient way to use opensearch and elasticsearch from Kotlin on any platform where Kotlin is usable.

Kt-search is extensible and modular. You can easily add your own custom DSLs for e.g. things not covered by this library or any custom plugins you use. And while it is opinionated about using e.g. kotlinx.serialization, you can also choose to use alternative serialization frameworks, or even use your own http client and just use the search-dsl.

Gradle

Add the maven.tryformation.com repository:

repositories {
    mavenCentral()
    maven("https://maven.tryformation.com/releases") {
        content {
            includeGroup("com.jillesvangurp")
        }
    }
}

And then the dependency to commonsMain or main:

    // check the latest release tag for the latest version
    implementation("com.jillesvangurp:search-client:2.x.y")

About maven central ... I've switched maven repositories a couple of times now. Jitpack and multiplatform just doesn't work. Of course I would have liked to get this on maven central. However, after repeated attempts to get that done, I've decided to not sacrifice more time on this. The (lack of) documentation, the Jira bureaucracy, the uninformative errors, the gradle plugin, etc. just doesn't add up to something that works for a multi module, multi platform project. I'm sure it can be done but I'm not taking more time out my schedule to find out.

If somebody decides to fix a proper, modern solution for hosting packages, I'll consider using it but I'm done with maven central for now. Google buckets work fine for hosting. So does ssh or any old web server. So does aws. It's just maven central that's a huge PITA.

Maven

If you have maven based kotlin project targeting jvm and can't use kotlin multiplatform dependency, you would need to add jvm targeting artifacts.

Add the maven.tryformation.com repository:

<repositories>
    <repository>
        <id>try-formation</id>
        <name>kt search repository</name>
        <url>https://maven.tryformation.com/releases</url>
    </repository>
</repositories>

And then add dependencies to jvm targets:

<dependencies>
    <dependency>
        <groupId>com.jillesvangurp</groupId>
        <artifactId>search-client-jvm</artifactId>
        <version>2.1.25</version>
    </dependency>
    <dependency>
        <groupId>com.jillesvangurp</groupId>
        <artifactId>search-dsls-jvm</artifactId>
        <version>2.1.25</version>
    </dependency>
    <dependency>
        <groupId>com.jillesvangurp</groupId>
        <artifactId>json-dsl-jvm</artifactId>
        <version>3.0.0</version>
    </dependency>
</dependencies>

Note: The json-dsl is moved to separate repository. To find the latest version number, check releases: https://github.com/jillesvangurp/json-dsl/releases

Usage

Create a client

First we create a client.

val client = SearchClient()

Kotlin has default values for parameters. So, we use sensible defaults for the host and port variables to connect to localhost and 9200.

val client = SearchClient(
  KtorRestClient(host="localhost", port=9200)
)

If you need ro, you can also configure multiple hosts, add ssl and basic authentication to connect to managed Opensearch or Elasticsearch clusters. If you use multiple hosts, you can also configure a strategy for selecting the host to connect to. For more on this, read the manual.

Documents and data classes

In Kotlin, the preferred way to deal with data would be a data class. This is a simple data class that we will use below.

@Serializable
data class TestDocument(
  val name: String,
  val tags: List<String>? = null
)

In the example below we will use this TestDocument, which we can serialize using the kotlinx.serialization framework. You can also pass in your own serialized json in requests, so if you want to use e.g. jackson or gson instead, you can do so easily.

Creating an index

val indexName = "readme-index"

// create an index and use our mappings dsl
client.createIndex(indexName) {
  settings {
    replicas = 0
    shards = 3
    refreshInterval = 10.seconds
  }
  mappings(dynamicEnabled = false) {
    text(TestDocument::name)
    keyword(TestDocument::tags)
  }
}

This creates the index and uses the mappings and settings DSL. With this DSL, you can map fields, configure analyzers, etc. This is optional of course; you can just call it without the block and use the defaults and rely on dynamic mapping. You can read more about that here

Adding documents

To fill the index with some content, we need to use bulk operations.

In kt-search this is made very easy with a DSL that abstracts away the book keeping that you need to do for this. The bulk block below creates a BulkSession, which does this for you and flushes operations to Elasticsearch. You can configure and tailor how this works via parameters that have sensible defaults. For example the number of operations that is flushed is something that you'd want to probably configure.

The optional refresh parameter uses WaitFor as the default. This means that after the block exits, the documents will have been indexed and are available for searching.

client.bulk(
  refresh = Refresh.WaitFor,
  // send operations every 2 ops
  // default would be 100
  bulkSize = 2,
) {
  index(
    doc = TestDocument(
      name = "apple",
      tags = listOf("fruit")
    ),
    index = indexName
  )
  index(
    doc = TestDocument(
      name = "orange",
      tags = listOf("fruit", "citrus")
    ),
    index = indexName,
  )
  index(
    // you can also provide raw json
    // but it has to be a single line in the bulk request
    source =
    """{"name":"banana","tags":["fruit","tropical"]}""",
    index = indexName
  )
}

You can read more about bulk operations in the manual.

Search

Now that we have some documents in an index, we can do some queries:

// search for some fruit
val results = client.search(indexName) {
  query = bool {
    must(
      // note how we can use property references here
      term(TestDocument::tags, "fruit"),
      matchPhrasePrefix(TestDocument::name, "app")
    )
  }
}

println("found ${results.total} hits")
results
  // extension function that deserializes
  // the hits using kotlinx.serialization
  .parseHits<TestDocument>()
  .first()
  .let {
    // we feel lucky
    println("doc ${it.name}")
  }
// you can also get the JsonObject if you don't
// have a model class
println(results.hits?.hits?.first()?.source)

This prints:

found 1 hits
doc apple
{"name":"apple","tags":["fruit"]}

You can also construct complex aggregations with the query DSL:

val resp = client.search(indexName) {
  // we don't care about retrieving hits
  resultSize = 0
  agg("by-tag", TermsAgg(TestDocument::tags) {
    // simple terms agg on the tags field
    aggSize = 50
    minDocCount = 1
  })
}
// picking the results apart is just as easy.
resp.aggregations
  .termsResult("by-tag")
  .parsedBuckets.forEach { bucket ->
    println("${bucket.parsed.key}: ${bucket.parsed.docCount}")
  }

This prints:

fruit: 3
citrus: 1
tropical: 1

These examples show off a few nice features of this library:

  • Kotlin DSLs are nice, type safe, and easier to read and write than pure Json. And of course you get auto completion too. The client includes more DSLs for searching, creating indices and mappings, datastreams, index life cycle management, bulk operations, aggregations, and more.
  • Where in JSON, you use a lot of String literals, kt-search actually allows you to use property references or enum values as well. So, refactoring your data model doesn't break your mappings and queries.
  • Kt-search makes complicated features like bulk operations, aggregations, etc. really easy to use and accessible. And there is also the IndexRepository, which makes it extremely easy to work with and query documents in a given index or data stream.
  • While a DSL is nice to have, sometimes it just doesn't have the feature you need or maybe you want to work with raw json string literal. Kt-search allows you to do both and mix schema less with type safe kotlin. You can easily add custom properties to the DSL via a simple put. All JsonDsl are actually mutable maps.
  • Kt-search is designed to be extensible. It's easy to use the built in features. And you can easily add your own features. This also works for plugins or new features that Elasticsearch or Opensearch add.

Manual

There are of course a lot more features that this library supports. The manual covers all of those.

Introduction

Search

Indices and Documents

Advanced Topics

Related projects

There are several libraries that build on kt-search:

  • kt-search-kts - this library combines kt-search with kotlinx-cli to make scripting really easy. Combined with the out of the box support for managing snapshots, creating template mappings, bulk indexing, data-streams, etc. this is the perfect companion to script all your index operations. Additionally, it's a great tool to e.g. query your data, or build some health checks against your production indices.
  • kt-search-logback-appender - this is a logback appender that bulk indexes log events straight to elasticsearch. We use this at FORMATION.
  • full stack Kotlin demo project A demo project that uses kt-search.
  • es-kotlin-client - version 1 of this client; now no longer maintained.

Additionally, I also maintain a few other search related projects that you might find interesting.

  • Rankquest Studio - A user friendly tool that requires no installation process that helps you build and run test cases to measure search relevance for your search products. Rankquest Studio of course uses kt-search but it is also able to talk directly to your API and is designed to work with any kind of search api or product that is able to return lists of results.
  • querylight - Sometimes Elasticsearch is just overkill. Query light is a tiny in memory search engine that you can embed in your kotlin browser, server, or mobile applications. We use it at FORMATION to support e.g. in app icon search. Querylight comes with its own analyzers and query language.

Setting up a development environment

Any recent version of Intellij should be able to import this project as is. This project uses docker for testing and to avoid having the tests create a mess in your existing elasticsearch cluster, it uses a different port than the default Elasticsearch port.

If you want to save some time while developing, it helps to start docker manually. Otherwise you have to wait for the container to stop and start every time you run a test.

docker-compose -f docker-compose-es-8.yml up -d

For additional details, refer to the build file.

Compatibility

The integration tests on GitHub Actions use a matrix build that tests everything against Elasticsearch 7 & 8 and Opensearch 1 & 2.

It may work fine with earlier Elasticsearch versions as well. But we don't actively test this and the tests are known to not pass with Elasticsearch 6 due to some changes in the mapping dsl. You may be able to work around some of this, however.

There is an annotation that is used to restrict APIs when needed. E.g. search-after only works with Elasticsearch and and has the following annotation to indicate that:

@VariantRestriction(SearchEngineVariant.ES7,SearchEngineVariant.ES8)
suspend fun SearchClient.searchAfter(target: String, keepAlive: Duration, query: SearchDSL): Pair<SearchResponse,Flow<SearchResponse.Hit>> {
    validateEngine("search_after does not work on OS1",
        SearchEngineVariant.ES7, 
        SearchEngineVariant.ES8)

    // ...
}

The annotation is informational only for now. In our tests, we use onlyon to prevent tests from failing on unsupported engines For example, this is added to the test for search_after:

onlyOn("opensearch has search_after but it works a bit different",
    SearchEngineVariant.ES7,
    SearchEngineVariant.ES8)

Module Overview

This repository contains several kotlin modules that each may be used independently.

Module Description
json-dsl Kotlin framework for creating kotlin DSLs for JSON dialects. Such as those in Elasticsearch.
search-dsls DSLs for search and mappings based on json-dsl.
search-client Multiplatform REST client for Elasticsearch 7 & 8 and Opensearch 1. This is what you would want to use in your projects.
docs Contains the code that generates the manual and this readme..

The search client module is the main module of this library. I extracted the json-dsl module and search-dsls module with the intention of eventually moving these to separate libraries. Json-dsl is actually useful for pretty much any kind of json dialect and I have a few APIs in mind where I might like to use it. The choice to not impose kotlinx.serialization on json dsl also means that both that and the search dsl are very portable and only depend on the Kotlin standard library.

Contributing

Pull requests are very welcome! Please consider communicating your intentions in advance to avoid conflicts, or redundant work.

Some suggestions of things you could work on:

  • Extend the mapping or query DSLs. Our goal is to have coverage of all the common things we and other users need. The extensibility of JsonDsl always gives you the option to add whatever is not directly supported by manipulating the underlying map. But creating extension functions that do this properly is not har.
  • Add more API support for things in Opensearch/Elasticsearch that are not currently supported. The REST api has dozens of end point other than search. Like the DSL, adding extension functions is easy and using the underlying rest client allows you to customize any requests.
  • Work on one of the issues or suggest some new ones.

Support and Community

Please file issues if you find any or have any suggestions for changes.

Within reason, I can help with simple issues. Beyond that, I offer my services as a consultant as well if you need some more help with getting started or just using Elasticsearch/Opensearch in general with just about any tech stack. I can help with discovery projects, trainings, architecture analysis, query and mapping optimizations, or just generally help you get the most out of your search setup and your product roadmap.

The best way to reach me is via email if you wish to use my services professionally. Please refer to my website for that.

I also try to respond quickly to issues. And I also lurk in the amazing Kotlin, Elastic, and Search Relevancy Slack communities.

About this README

This readme is generated using my kotlin4example library. I started developing that a few years ago when I realized that I was going to have to write a lot of documentation with code examples for kt-search. By now, both the manual and this readme heavily depend on this and it makes maintaining and adding documentation super easy.

The way it works is that it provides a dsl for writing markdown that you use to write documentation. It allows you to include runnable code blocks and when it builds the documentation it figures out how to extract those from the kotlin source files and adds them as markdown code snippets. It can also intercept printed output and the return values of the blocks.

If you have projects of your own that need documentation, you might get some value out of using this as well.

kt-search's People

Contributors

alexander-rychkov avatar anyroad avatar artemmukhin avatar barbulescu avatar bierdav avatar conorpfarrell avatar csh97 avatar dybarsky avatar goodhoko avatar hanswesterbeek avatar jhult avatar jillesvangurp avatar larsgraedig avatar lddelchev avatar mixify avatar samuelfa avatar sinkyoungdeok avatar technobasi avatar the-eater avatar yassenb avatar yongheecheon avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

kt-search's Issues

[FEAT] top_hits aggregation is not implemented

Describe the enhancement

missing aggregation: top_hits

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html

How do you think it should be done?

class TopHitsAggConfig : JsonDsl() {
    var aggSize by property<Long>("size") // can't redefine Map.size sadly
    var from by property<Long>()
    var sort by property<List<Any>>()
}

fun TopHitsAggConfig.sort(block: SortBuilder.() -> Unit) {
    val builder = SortBuilder()
    block.invoke(builder)
    sort = builder.sortFields
}

class TopHitsAgg(block: (TopHitsAggConfig.() -> Unit)? = null) : AggQuery("top_hits") {
    init {
        val config = TopHitsAggConfig()
        block?.invoke(config)
        put(name, config)
    }
}
@Serializable
data class TopHitsAggregationResult(
    val hits: List<SearchResponse.Hit>,
)
fun Aggregations?.topHitResult(name: String, json: Json = DEFAULT_JSON): TopHitsAggregationResult =
    getAggResult(name, json)

Will you be able to help with a pull request?

probably will open a pull request once enough stuff accumulates

[FEAT] _delete_by_query not implemented

Describe the enhancement

The _delete_by_query is not implemented yet.

See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html#docs-delete-by-query-api-request

Why is this needed?

A useful method of deleting a large set of documents without having to remove them individually.

How do you think it should be done?

A deleteBy or deletebyQuery function on the SearchClient (and perhaps on the IndexRepository that allows users to delete documents based on a query. I believe a large part of the searchDsl could be reused.

For the moment, I am using this extension function, but this is quite limited and I have only implemented everyting necessairy for my own purposes:

    private suspend fun SearchClient.deleteByQuery(target: String?, block: (SearchDSL.() -> Unit)? = null): JsonObject {
        val dsl = SearchDSL()
        block?.invoke(dsl)

        return this.restClient.post {
            path(target, "_delete_by_query")

            val rawJson = dsl.json()
            if (rawJson.isNotBlank()) {
                rawBody(rawJson)
            }
        }.parseJsonObject()
    }

Will you be able to help with a pull request?
Given that this is my first time working with Elasticsearch (and this library, which works great by the way!), I doubt I can be of much assistance. Perhaps if this feature still missing after a while that I will try to tackle it.

[FEAT] Investigate using annotations for implementation specific features

There are going to be APIs and API extensions that only work with newer versions of Elasticsearch or Opensearch or that work differently on both.

  • create some annotation to mark such features
  • provide run-time warnings if the user tries to use an unsupported feature with the wrong search client
  • add version sniffing option to the rest-client

[BUG] Searching over multiple indices + ids query not working

Describe the bug
In advance sorry for bringing these two things as one issue but I don't want to spam it here with questions that might not be "bugs" but more of wrong usage of the library.

I would like to be able query multiple indices - at first I was looking for some specific method but I didn't find it. Then I thought it would be enough to pass it as "index1, index2" string but that throws exception "no such index [ index2]". That looks like it somehow might support multiple indices (as it's printed as array) search and I am using it wrong but even when a dig a bit in a search-api.kt class I wasn't successful to find how it should be done or if it's supported at all?

I was used to use the ids with es-kotlin-client but after migration it somehow doesn't work. If I do term query with exact value (single one) it retrieves document as it should. But once I try to use ids query where I convert List<String> to typedArray and then I use the * spread operator it's not working and I receive zero documents for the same document id. Again I tried to dig a bit in term-level-queries.kt class and I suspect that it might be because block is set to null by default? and then it's invoked even tho it seems that here:

init {
        this["values"] = values
        block?.invoke(this)
    }

it's initialised.

To Reproduce
Steps to reproduce the behavior:

val response = client.search("index, index2") {
            query = ids(*ids.toTypedArray())
        }.parseHits<Document>()
val documents = listOf("test")
val response = client.search("default") {
            query = ids(*documents.toTypedArray())
        }.parseHits<Document>()

Expected behavior

Possible usage of search for multiple indices.
Ids SearchDSL should provide possibility to search via multiple ids.

Your context

Kotlin version: 1.7.10
Search client version: search-client:1.99.9
Version of Elasticsearch: "number" : "7.17.1",

Will you be able to help with a pull request?

Optional of course, but do let me know if you plan to do work.

[FEAT] bulk callback handler similar to legacy client

The legacy client has a callback mechanism that allows you to have per item callbacks. This is highly useful as the API will return a 200 and require you to dig through item responses to identify failures and conflicts.

Other features to consider:

  • fail fast mode: throw an exception on the first failure
  • dynamically back off in case of the server being overwhelmed

[FEAT] Configure a client without a port

Describe the enhancement

I would like to be able to specify a connection URL without a port.

Why is this needed?

My ES cluster has a proxy in front of it and the API responds when no port is specified but does not responds if I specify 80 as port. So, right now, I can't use kt-search at all (unless I missed something in the docs, but I checked the code and it seems that port is non nullable in Node so....)

Will you be able to help with a pull request?

I sure can. Maybe it is possible to just make port nullable in Node data class, but it may have implications that I don't suspect... :)

Cheers

[FEAT] Support more of the ILM parameters

Describe the enhancement

Currently only a subset is supported. Things like max_age are not supported

Why is this needed?

Nice to have; can work around it by manipulating the jsondsl map.

[FEAT] Support netty/armeria client

Describe the enhancement

Support a rest client other than Ktor such as Netty or Armeria.

Why is this needed?

We don't want to depend on Ktor when we already have dependencies on Netty and Armeria.

How do you think it should be done?

Should be a simple implementation. I think we should either pack it as a several jar or assume those two clients are runtime libraries.

Will you be able to help with a pull request?

Yes, I am willing to but first want to hear from the author about this request.

[FEAT] Make it possible to create/update an index using raw JSON to define the mappings and settings

Describe the enhancement

Make it possible to create/update an index using raw JSON to define the mappings and settings

Why is this needed?

It would be really convenient to be able to store serialized JSON snapshots of the mapping state (client.getIndex(<INDEX_NAME>).toString()) and then use those snapshots to generate a IndexSettingsAndMappingsDSL object that could be used to roll back to the previous state.

How do you think it should be done?

There's a couple of options I can think of:

  1. Add a createIndex signature that takes a rawJson defining the index mappings + settings
  2. Add a deserialize method to JsonDsl (I'm not very familiar with Kotlin/JSON serialization, so I'm unsure of how much work it would be to enable the classes that inherit from JsonDsl to deserialize)

Will you be able to help with a pull request?

I should be able to help.

[BUG] multi-search is not correctly parsed

Describe the bug

A clear and concise description of what the bug is.

elasticsearch responds to <target?_msearch with a json object, but kt-search is trying to parse nd-json

also.. kt-search is currently dropping the content-header even if it is set using rawBody(body, contentType)

To Fix

the response needs to be parsed with

@Serializable
data class MultiSearchResponse(
    val took: Long,
    val responses: List<SearchResponse>
)

suspend fun SearchClient.msearch(
    target: String?=null,
    body: String?,
    allowNoIndices: Boolean? = null,
    cssMinimizeRoundtrips: Boolean? = null,
    expandWildcards: ExpandWildCards? = null,
    ignoreThrottled: Boolean? = null,
    ignoreUnavailable: Boolean? = null,
    maxConcurrentSearches: Int? = null,
    maxConcurrentShardRequests: Int? = null,
    preFilterShardSize: Int? = null,
    routing: String? = null,
    searchType: SearchType? = null,
    typedKeys: Boolean? = null,
): MultiSearchResponse {
    return restClient.post {
        path(*listOfNotNull(target.takeIf { !it.isNullOrBlank() }, "_msearch").toTypedArray())

        parameter("allow_no_indices", allowNoIndices)
        parameter("ccs_minimize_roundtrips", cssMinimizeRoundtrips)
        parameter("expand_wildcards", expandWildcards)
        parameter("ignore_throttled", ignoreThrottled)
        parameter("ignore_unavailable", ignoreUnavailable)
        parameter("max_concurrent_searches", maxConcurrentSearches)
        parameter("max_concurrent_shard_requests", maxConcurrentShardRequests)
        parameter("max_concurrent_shard_requests", maxConcurrentShardRequests)
        parameter("pre_filter_shard_size", preFilterShardSize)
        parameter("routing", routing)
        parameter("search_type", searchType)
        parameter("typed_keys", typedKeys)

        body?.let {
            rawBody(
                body = body,
                contentType = ContentType("application", "x-ndjson")
            )
        }
    }.parse(MultiSearchResponse.serializer())
}

and to fix the content-type not being passed along (although it works without it anyways.. just not quite like the elasticsearch docs say)

suspend fun RestClient.get(block: SearchAPIRequest.() -> Unit): Result<RestResponse.Status2XX> {
    val request = SearchAPIRequest()
    block.invoke(request)
    return doRequest(
        pathComponents = listOf("/" + request.pathComponents.joinToString("/")),
        payload = request.body,
        contentType = request.contentType,
        httpMethod = HttpMethod.Get,
        parameters = request.parameters,
        headers = request.headers
    ).asResult()
}

Expected behavior

A clear and concise description of what you expected to happen.

Your context

  • version: 2.0.0-RC-6

Will you be able to help with a pull request?

yes, ofc

[BUG] Missing authentication crendetials for REST request

Describe the bug
I recently switch from es-kotlin-client but I'm unable to connect to the ES cluster for basic search. I suspect this might be an issue with settings of my ES cluster but since it was working with previous client I'm not sure so posting here first. I found following thread with similar problem here. I guess it might be related to switching to different version of Rest client under the hood? Because all settings (host, port, user) is kept the same from previous usage.

By the observations it seems that it doesn't matter if I put wrong/correct credentials and it still keeps giving the same result - hence it seems it's really not used.

To Reproduce
Steps to reproduce the behavior:

I'm using following setup for ES client (as I was used to before):

private val client = SearchClient(
        KtorRestClient(
            host = host,
            port = port,
            https = https,
            user = user,
            password = password
        )
    )

and ten I just want to search by ids

val response = client.search(index) {
            query = ids(*ids)
        }

and result is:

07:42:03.656 [eventLoopGroupProxy-4-1 @call-handler#11] ERROR ktor.application - Unhandled: GET - /v1/document/search/
com.jillesvangurp.ktsearch.RestException: RequestIsWrong 401: {"error":{"root_cause":[{"type":"security_exception","reason":"missing authentication credentials for REST request [/default/_search]","header":{"WWW-Authenticate":"Basic realm=\"security\" charset=\"UTF-8\""}}],"type":"security_exception","reason":"missing authentication credentials for REST request [/default/_search]","header":{"WWW-Authenticate":"Basic realm=\"security\" charset=\"UTF-8\""}},"status":401}
	at com.jillesvangurp.ktsearch.RestclientKt.asResult(Restclient.kt:91)
	at com.jillesvangurp.ktsearch.Request_dslKt.post(request-dsl.kt:70)
	at com.jillesvangurp.ktsearch.Request_dslKt$post$1.invokeSuspend(request-dsl.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at io.ktor.util.pipeline.SuspendFunctionGun.resumeRootWith(SuspendFunctionGun.kt:138)
	at io.ktor.util.pipeline.SuspendFunctionGun.loop(SuspendFunctionGun.kt:112)
	at io.ktor.util.pipeline.SuspendFunctionGun.access$loop(SuspendFunctionGun.kt:14)
	at io.ktor.util.pipeline.SuspendFunctionGun$continuation$1.resumeWith(SuspendFunctionGun.kt:62)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:503)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.ktor.server.netty.EventLoopGroupProxy$Companion.create$lambda-1$lambda-0(NettyApplicationEngine.kt:285)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.base/java.lang.Thread.run(Thread.java:832)

Expected behavior
Credentials passed in KtorRestClient should be used as it somehow seems that they are missing or invalid.

Your context

  • what versions of software are you using
  • what operating system are you using
  • whatever else you think is relevant

Kotlin version: 1.7.10
Search client version: search-client:1.99.5
Version of Elasticsearch: "number" : "7.17.1",

Will you be able to help with a pull request?

Optional of course, but do let me know if you plan to do work.

[BUG] Bulk update using doc

I tried bulk update from version 2.0.0-RC-7 but got an issue, the values has double quote

this is my code

searchClient.bulk(refresh = Refresh.WaitFor) {
   update(
       doc = TestDocument(name="test", age= 20),
       index = "es-index",
       id = "1",
   )
}

expected result

{
   "name": "test",
   "age": 20
}

actual result

{
   "name": "\"test\"",
   "age": "20"
}

ES Version 7.12.1

[FEAT] repository class

  • Similar to the repository class in the legacy-client
  • Try to stay close to the old API to minimize migration issues
  • Figure out pluggable json strategy for serializing/deserializing model classes

[BUG] intermittent test failure

Looks like we need to delete templates before recreating.

DocumentationTest > documentation() FAILED
    java.lang.ExceptionInInitializerError
        at documentation.manual.Manual_indexKt.<clinit>(manual-index.kt:55)
        at documentation.DocumentationTestKt.<clinit>(DocumentationTest.kt:40)
        at documentation.DocumentationTest.documentation(DocumentationTest.kt:52)

        Caused by:
        com.jillesvangurp.ktsearch.RestException: RequestIsWrong 400: {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"index template [my-logs-template] has index patterns [logs*] matching patterns from existing templates [my-template] with patterns (my-template => [logs*]) that have the same priority [0], multiple index templates may not match during index creation, please use a different priority"}],"type":"illegal_argument_exception","reason":"index template [my-logs-template] has index patterns [logs*] matching patterns from existing templates [my-template] with patterns (my-template => [logs*]) that have the same priority [0], multiple index templates may not match during index creation, please use a different priority"},"status":400}
            at app//com.jillesvangurp.ktsearch.RestclientKt.asResult(Restclient.kt:92)
            at app//com.jillesvangurp.ktsearch.Request_dslKt.put(request-dsl.kt:131)
            at app//com.jillesvangurp.ktsearch.Request_dslKt$put$1.invokeSuspend(request-dsl.kt)
            at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
            at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
            at app//kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
            at app//kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
            at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
            at app//kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
            at app//kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
            at app//kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
            at app//com.jillesvangurp.kotlin4example.Kotlin4Example.suspendingBlock(Kotlin4Example.kt:307)
            at app//com.jillesvangurp.kotlin4example.Kotlin4Example.suspendingBlock(Kotlin4Example.kt:271)
            at app//com.jillesvangurp.kotlin4example.Kotlin4Example.suspendingBlock$default(Kotlin4Example.kt:259)
            at app//documentation.manual.bulk.indexmanagement.DatastreamsKt$dataStreamsMd$1$1.invoke(datastreams.kt:23)
            at app//documentation.manual.bulk.indexmanagement.DatastreamsKt$dataStreamsMd$1$1.invoke(datastreams.kt:21)
            at app//com.jillesvangurp.kotlin4example.Kotlin4Example.section(Kotlin4Example.kt:60)
            at app//documentation.manual.bulk.indexmanagement.DatastreamsKt$dataStreamsMd$1.invoke(datastreams.kt:21)
            at app//documentation.manual.bulk.indexmanagement.DatastreamsKt$dataStreamsMd$1.invoke(datastreams.kt:11)
            at app//com.jillesvangurp.kotlin4example.Kotlin4Example$Companion.markdown(Kotlin4Example.kt:426)
            at app//com.jillesvangurp.kotlin4example.SourceRepository.md(SourceRepository.kt:10)
            at app//documentation.manual.bulk.indexmanagement.DatastreamsKt.<clinit>(datastreams.kt:11)
            ... 3 more

[DOCS] Would be nice to have something about searching for non-string values.

Describe the issue

I'm trying to query on an integer field. I have the below working, but it took some digging to work out - could you drop something like this into the docs? (or something better!)

        query =
            bool {
                filter(
                    listOf(
                        disMax {
                            queries(
                                listOf(
                                   ESQuery("match_phrase", JsonDsl().apply {
                                        put("tc_build_id", 18941877)
                                    }),
                                    ESQuery("match_phrase", JsonDsl().apply {
                                        put("tc_build_id", 18992070)
                                    })
                                )
                            )
                        }
                        , matchPhrase("type", "scenario")
                    )
                )
            }

And I know I could lose the 'listOf' calls, but I'm leaving them in because I need to build the lists programmatically in my final code. :)

Will you be able to help with a pull request?

Sure - let me know if the above is acceptable as an example, and where you want it.

[FEAT] scrolling / deep paging searches

Similar to what the legacy client did

  • explore different new APIs for this (scrolling was deprecated some time ago)
  • make sure it works across opensearch/elasticsearch
  • use flows, like the legacy client

[BUG] Multi-word configuration properties for match queries don't work

Describe the bug

Setting MatchQueryConfig.maxExpansions doesn't work

To Reproduce

Steps to reproduce the behavior:
Configure a match query with maxExpansions = 100 (any number works) and execute the query. An error "[match] query does not support [maxExpansions]" is returned because the client passes "maxExpansions": 100 as part of the JSON sent to the OpenSearch server

Expected behavior

"max_expansions": 100 should be passed to the OpenSearch server

Your context

latest version 1.99.14 on Ubuntu 22.04

Will you be able to help with a pull request?

Sure, I can create a PR

Overall I think the default for the JsonDsl constructor should be namingConvention = PropertyNamingConvention.ConvertToSnakeCase. That's the naming convention for OpenSearch after all. Naming things camelCase should be the exception, not the rule

PIT support for Opensearch 2.7/2.8

Hi gilles,

This issue is to exchange thoughts on how to support the above. I would like to check a few things with you before I create a PR.

In the release-notes for 2.0.4 you write:

_pit API seems no longer supported in the latest Opensearch 2.7 release. I've restricted searchAfter to not allow OS1/OS2 anymore. If somebody needs this, please create a PR to add support for the new APIs in Opensearch. Or you can use scrolling searches

This surprised me because it appears in the docs for both versions 2.7 and 2.8

I also checked the docs of the Elastic counterpart, and I see no differences between the two.

What am I missing? Because from the looks of it, it seems to me it would 'just' work?

Full disclosure: In the end, what I will need is PIT-based slicing support :)

[FEAT] Implement highlighting

Describe the enhancement

Add support for highlighting

Why is this needed?

To highlight search terms.

How do you think it should be done?

Add another option to the SearchDSL, as well as to the SearchResponse. You can circumvent the SearchDSL by using, but (as far as I can tell), you cannot retrieve the highlight field from the output:

this["highlight"] = withJsonDsl {
    this["fields"] = withJsonDsl {
        this["field_name"] = withJsonDsl { }
        this["another_field_name"] = withJsonDsl { }
    }
}

Will you be able to help with a pull request?
Of course!

[FEAT] adding routing argument for IndexRepository.search() and deleteByQuery()

Describe the enhancement

IndexRepository.search() and deleteByQuery() methods allow routing argument

Why is this needed?

I prefer using IndexRepository to using SearchClient directly.
But when I have to query with routing parameter, it is inevitable to use SearchClient.

How do you think it should be done?

IndexRepository.search() and deleteByQuery() methods allow routing argument to be passed to internal SearchClient.

Will you be able to help with a pull request?

If you grant me permission to create new branch, I can make PR for this issue.
or you just can modify it referencing below.

    // IndexRepository.kt
    suspend fun search(rawJson: String, routing: String? = null): SearchResponse {
        return client.search(target = indexReadAlias, rawJson = rawJson, routing = routing)
    }

    suspend fun search(dsl: SearchDSL, routing: String? = null): SearchResponse {
        return client.search(target = indexReadAlias, dsl = dsl, routing = routing)
    }

    suspend fun search(routing: String? = null, block: SearchDSL.() -> Unit): SearchResponse {
        return client.search(target = indexReadAlias, block = block, routing = routing)
    }

    suspend fun deleteByQuery(routing: String? = null, block: SearchDSL.() -> Unit): DeleteByQueryResponse {
        return client.deleteByQuery(target = indexWriteAlias, block = block, routing = routing)
    }

[FEAT] Deploy to Maven Central

Describe the enhancement

Deploys artifacts, sources etc to Maven Central.

Why is this needed?

There are lots of organizations that will not allow their devs to use libs that do not at least appear with the appropriate GPG signatures. This hampers adoption and growth of community for kt-search.

How do you think it should be done?

[Technically, this is how]
](https://h4pehl.medium.com/publish-your-gradle-artifacts-to-maven-central-f74a0af085b1) Some additional credentials also need to be stup.

Will you be able to help with a pull request?

Yes, happy to help out and integrate it into github actions, but would have to be given permissions for it.

[BUG] Can't access Settings and Mapping DSL

Describe the bug

So the problem is that the IntelliJ Idea can't resolve this class com.jillesvangurp.searchdsls.mappingdsl.IndexSettingsAndMappingsDSL and therefore you get an Unresolved reference: settings on line 20.

To Reproduce

build.gradle.kts

repositories {
    mavenCentral()
    maven("https://maven.tryformation.com/releases") {
        content {
            includeGroup("com.jillesvangurp")
        }
    }
}

dependencies {
    implementation("io.ktor:ktor-server-core-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-auth-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-sessions-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-caching-headers-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-cors-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-hsts-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-http-redirect-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-partial-content-jvm:$ktorVersion")
    implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-websockets-jvm:$ktorVersion")
    implementation("io.ktor:ktor-server-cio-jvm:$ktorVersion")
    implementation("ch.qos.logback:logback-classic:$logbackVersion")
    testImplementation("io.ktor:ktor-server-tests-jvm:$ktorVersion")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlinVersion")

    // Database
    implementation("com.jillesvangurp:search-client:2.0.0-RC-2")

    // Kotlin scripting
    implementation("org.jetbrains.kotlin:kotlin-scripting-common")
    implementation("org.jetbrains.kotlin:kotlin-scripting-jvm")
    implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies")
    implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven")
}

Application.kt:

import com.jillesvangurp.ktsearch.KtorRestClient
import com.jillesvangurp.ktsearch.SearchClient
import com.jillesvangurp.ktsearch.createIndex
import io.ktor.server.application.*
import io.ktor.server.cio.*
import kotlinx.coroutines.runBlocking


fun main(args: Array<String>): Unit =
    EngineMain.main(args)

@Suppress("unused")
fun Application.module() {
    val client = SearchClient(KtorRestClient(user = "elastic", password = "---"))
    runBlocking {
        client.createIndex("languages") {
            settings {

            }
        }
    }
}

image

Expected behavior

No error

Your context

This is probably a configuration problem when you compare the structure of the libraries you can see that search-dsls features another structure as the other two modules:
image

Will you be able to help with a pull request?

I have already submitted a pull request, but not sure if it solves the problem

[BUG] Bulk request with "source" parameter crashes

Bulk request with source parameter throws error:
com.jillesvangurp.ktsearch.RestException: RequestIsWrong 400: {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"request [/recipients/_bulk] contains unrecognized parameter: [source] -> did you mean [_source]?"}],"type":"illegal_argument_exception","reason":"request [/recipients/_bulk] contains unrecognized parameter: [source] -> did you mean [_source]?"},"status":400}

To Reproduce

repo.bulk(bulkSize = UPSERT_BATCH, refresh = refreshPolicy, callBack = itemCallback, source = "true") {
...
}

Also there is no source field in callback object.

v2.0.0-RC-6
ES v7.7.1

[FEAT] Add support for _count endpoint

Describe the enhancement

What would you like changed?

Add support for count queries: https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html

Why is this needed?

This allows more complex pagination flows.

How do you think it should be done?

It seems that a count method could be implemented reusing most of the search logic and just changing the endpoint + response handling.

Will you be able to help with a pull request?

I am not planning to do the work at this point in time (if needed I may do it in the future if you don't have time).

[FEAT] add user configurable query params and headers

Allow users to specify additional query parameters and headers on all search APIs.

  • Similar to the default request options in the RestHighLevel client.
  • Allow setting this on both SearchClient and each of the extension functions.

[BUG] indexDocument version should be a Long

The version param in SearchClient.indexDocument should be of type Long instead of Int to support all usecases (eg using millis-since-epoch as version)

see also Elasticsearch Docs on indexing:

The value provided must be a numeric, long value greater than or equal to 0, and less than around 9.2e+18.

As a workaround, one can use extraParameters

   val version = 1697624118596L
   client.indexDocument(
                indexName,
                doc
                id = id,
//                version = version.toInt(),
                versionType = VersionType.External,
                extraParameters = mapOf(
                    "version" to "$version"
                ),

Your context

  • search-client 2.1.1"

Will you be able to help with a pull request?

no, sorry ;-)

[FEAT] Date format in date histogram aggregation

Describe the enhancement

I'd like to be able to choose the date format in a DateHistogramAgg.

Why is this needed?

It's an ElasticSearch feature, so it should be in this lib as well.

How do you think it should be done?

The property format should be added to the DateHistogramAggConfig class, like so:

class DateHistogramAggConfig : JsonDsl() {
    var field by property<String>()
    var calendarInterval by property<String>("interval")
    var format by property<String>("format") // <--
    var minDocCount by property<Long>("min_doc_count")
}

Will you be able to help with a pull request?

Yes.

[FEAT] update by query

Hello,
in the first version of the lib it was possible to run an update by query with a script,
but can't find similar functionality in current version

fun <T : Any> IndexRepository<T>.updateByQuery(
    esClient: RestHighLevelClient,
    script: Script,
    query: QueryBuilder,
) {
    val index = this.indexName
    val updateByQueryRequest = UpdateByQueryRequest(index)
    updateByQueryRequest.script = script
    updateByQueryRequest.setQuery(query)
    esClient.updateByQuery(
        updateByQueryRequest,
        RequestOptions.DEFAULT,
    )
}

[FEAT] Support JVM 11

Describe the enhancement

Would it be possible to configure the library to create a Java 11 release as well?

Why is this needed?

Our use case: The code that's using the KtSearch library is currently at Java 11 and updating would involve a lot of cascading updates to other internal packages.

We were previously using 1.99.16, but are being forced to update because we're seeing this issue that was fixed. We're also having trouble updating to v1.99.17 because of the old jitpack issues. The issues are not specific to the version because the jvmTarget was changed in the the same commit as the jitpack issues were fixed.

How do you think it should be done?

  • the only documentation I could find about deploying multiple JVM version for a library was talking about native Java libraries and I wasn't actually able to confirm whether this is possible with Kotlin Multiplatform.
  • Would it be possible to get some more context on why the move to JVM 17 was made? The basic exploration we've done so far doesn't show any requirements that would not be available in JVM 11 as of the latest version
    • if you have no plans to support JVM 11, we can probably fork the library and deploy it to a private repo, but we'd obviously prefer to not have the extra overhead
    • would it be possible for the library to to go back to JVM 11 or do dual releases for the major versions?
  • would it be possible to backfill-publish the older versions to tryformation?

Will you be able to help with a pull request?

I don't have a lot of experience with Gradle/Kotlin multiplatform, but I can try to help.

cc @gillianchesnais @sajandrews

[BUG] `java.lang.ArrayIndexOutOfBoundsException` at `at com.jillesvangurp.ktsearch.RoundRobinNodeSelector.selectNode(RoundRobinNodeSelector.kt:6)`

Describe the bug

When querying an AWS hosted OpenSearch 2.3 service with 3 nodes we get the following exception:

java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 1
	at com.jillesvangurp.ktsearch.RoundRobinNodeSelector.selectNode(RoundRobinNodeSelector.kt:6)
	at com.jillesvangurp.ktsearch.KtorRestClient.nextNode(KtorRestClient.kt:45)
	at com.jillesvangurp.ktsearch.KtorRestClient.doRequest(KtorRestClient.kt:56)
	at com.jillesvangurp.ktsearch.RestClient$DefaultImpls.doRequest$default(Restclient.kt:18)
	at com.jillesvangurp.ktsearch.Request_dslKt.post(request-dsl.kt:65)
	at com.jillesvangurp.ktsearch.Search_apiKt.search-yk69jQ8(search-api.kt:266)
	at com.jillesvangurp.ktsearch.Search_apiKt.search-yk69jQ8(search-api.kt:166)
	at com.jillesvangurp.ktsearch.Search_apiKt.search-mdzbyIQ(search-api.kt:70)
	at com.jillesvangurp.ktsearch.Search_apiKt.search-mdzbyIQ$default(search-api.kt:21)
	at com.jillesvangurp.ktsearch.repository.IndexRepository.search(IndexRepository.kt:279)
	at com.transportapi.placesSearch.search.PlaceIndex$query$1.invokeSuspend(PlaceIndex.kt:320)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:284)
	at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:85)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
	at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
	at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
	at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)

To Reproduce

The error is triggered by a call to the search()

Expected behavior

The node selection should not fail.

Your context

com.jillesvangurp:search-client:1.99.13
AWS hosted OpenSearch 2.3

Also, on subsequent calls it seems the index is just kept increasing and the exceptions looks like:

java.lang.ArrayIndexOutOfBoundsException: Index 6 out of bounds for length 1

ย 

Will you be able to help with a pull request?

A guard condition fix looks trivial but without deeper knowledge of the library structure I'm not 100% sure this is the correct approach. If you are happy with it we can open a PR.

[FEAT] Ability to get errors

Describe the enhancement

I would like the Status of the request easily available. If it is already easily available, I would like to see it in the docs. I would also like to see tests that fail intentionally and check to see if it fails.

Why is this needed?

In the previous version of the library the results returned were the objects from the ElasticSearch lib (IndexResponse etc.). These objects have the public RestStatus status() function. This allows me to check if my request was successful.

I have been staring at it for a while and I think DocumentIndexResponse.result might be the place to check.
Or maybe it is in DocumentIndexResponse.shards.successful?
Or DocumentIndexResponse.shards.failed?
Why are these Int fields?

if (response.status().equals(RestStatus.CREATED) || response.status().equals(RestStatus.OK)) {
    return Ok(uuid)
}

How do you think it should be done?

I thought maybe a try/catch, but everything is wrapped in Result types, which is great! But then the Result type is unwound and put into the DocumentIndexResponse so I can't tell if it was successful or not.

Hopefully its just some documentation. Or maybe I'll figure this out by myself with what is available. Thanks.

[FEAT] Filter aggregations

Describe the enhancement

I'd like to be able to do filter aggregations, like so:

"aggs": {
  "my-agg-name": {
    "filter": {
      "bool": {
        "must": [{
          "nested": {
            "path": "someField",
            "query": {
              "bool": {
                "must": [{
                  "exists": { "field": "someField" }
                }, {
                  "term": {"someField.somePath": "someValue"}
                }]
              }
            }
          }
        }]
      }
    }
  },
  ...
}

Why is this needed?

It's an ElasticSearch feature, so it should be in this lib as well.

How do you think it should be done?

I think an extension to AggQuery can be made which uses a BoolQuery as the "body".

Will you be able to help with a pull request?

Maybe.

investigate test failures with es 6

Currently the tests don't pass with Elasticsearch v6. As there were not a lot of changes between 6 and 7, it may be possible to get them to pass.

Run elasticsearch like so:

docker run -p 9999:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:6.8.23

Run the tests with

./gradlew :search-client:build

Multiple tests fail trying to create the test document index:

ElasticsearchStatusException[Elasticsearch exception [type=mapper_parsing_exception, reason=Failed to parse mapping [properties]: Root mapping definition has unsupported parameters:  [number : {type=double}] [tag : {type=keyword}] [message : {type=text}]]]; nested: ElasticsearchException[Elasticsearch exception [type=mapper_parsing_exception, reason=Root mapping definition has unsupported parameters:  [number : {type=double}] [tag : {type=keyword}] [message : {type=text}]]];

Not super critical, but would be nice to figure this out.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.