Giter Club home page Giter Club logo

elasticswift's Introduction

ElasticSwift

Build Status codecov Swift Version SwiftPM Compatible Supported Platforms CocoaPods Compatible

Project Status

This project is actively in developement, more information will be made available as project progresses.

If you'd like to contribute pull requests are welcome.

  • Platform support for macOS, iOS & linux.

  • Query DSL builders and helpers similar to elasticsearch Java client. Check the table below to see full list of avaiable QueryBuilders

Project Goal

Our goal is to make a very Swifty and high-performant elasticsearch client.

High-performant means providing end user's response under 100ms not including the time elasticsearch took to process the request.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

CocoaPods 1.6.0+ is required to build ElasticSwift.

To integrate Elasticswift into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '10.0'
use_frameworks!

target '<Your Target Name>' do
    pod 'ElasticSwift', '~> 1.0.0-beta.1'
    pod 'ElasticSwiftCore', '~> 1.0.0-beta.1'
    pod 'ElasticSwiftQueryDSL', '~> 1.0.0-beta.1'
    pod 'ElasticSwiftCodableUtils', '~> 1.0.0-beta.1'
    pod 'ElasticSwiftNetworking', '~> 1.0.0-beta.1'
end

Note:- ElasticSwiftNetworkingNIO is not available as a pod

Then, run the following command:

$ pod install

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but ElasticSwift does support its use on supported platforms.

Once you have your Swift package set up, adding ElasticSwift as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/pksprojects/ElasticSwift.git", from: "1.0.0-beta.1")
]

and then adding the appropriate ElasticSwift module(s) to your target dependencies. The syntax for adding target dependencies differs slightly between Swift versions. For example, if you want to depend on the ElasticSwift and ElasticSwiftCore modules, specify the following dependencies:

Swift 5.0 and 5.1 (swift-tools-version:5.[01])

    dependencies: ["ElasticSwift", "ElasticSwiftCore"]

Swift 5.2 (swift-tools-version:5.2)

    dependencies: [.product(name: "ElasticSwift", package: "elastic-swift"),
                   .product(name: "ElasticSwiftCore", package: "elastic-swift")]

Usage

Client

Creating Settings & ElasticClient.

Using ElasticSwiftNetworking (URLSession based implementation)

import ElasticSwift
import ElasticSwiftNetworking

var settings = Settings(forHost: "http://localhost:9200", adaptorConfig: URLSessionAdaptorConfiguration.default) // Creates default settings for client
var client = ElasticClient(settings: settings) // Creates client with specified settings

Using ElasticSwiftNetworkingNIO (SwiftNIO/AsyncHTTPClient based implementation)

import ElasticSwift
import ElasticSwiftNetworkingNIO

var settings = Settings(forHost: "http://localhost:9200", adaptorConfig: AsyncHTTPClientAdaptorConfiguration.default) // Creates default settings for client
var client = ElasticClient(settings: settings) // Creates client with specified settings

Add Elasticsearch credentials

let cred = BasicClientCredential(username: "elastic", password: "elastic")
let settings = Settings(forHost: "http://localhost:9200", withCredentials: cred, adaptorConfig: AsyncHTTPClientAdaptorConfiguration.default)

Configuring SSL when using ElasticSwiftNetworking

let certPath = "/path/to/certificate.der"
let sslConfig = SSLConfiguration(certPath: certPath, isSelf: true)
let adaptorConfig = URLSessionAdaptorConfiguration(sslConfig: sslConfig)
let settings = Settings(forHosts: ["https://samplehost:port"], withCredentials: cred, adaptorConfig: adaptorConfig)

Index

Create and Delete Index

func createHandler(_ result: Result<CreateIndexResponse, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

// creating index
let createIndexRequest = CreateIndexRequest("indexName")
client.indices.create(createIndexRequest, completionHandler: createHandler) // executes request

// delete index
func deleteHandler(_ result: Result<AcknowledgedResponse, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

let deleteIndexRequest = DeleteIndexRequest("indexName")
client.indices.delete(deleteIndexRequest, completionHandler: deleteHandler) // executes request

Document

Document CRUD

class MyClass: Codable, Equatable {
  var myField: String?
}

// index document
func indexHandler(_ result: Result<IndexResponse, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

let mySource = MyClass()
mySource.myField = "My value"

let indexRequest = try IndexRequestBuilder<MyClass>()
        .set(index: "indexName")
        .set(type: "type")
        .set(id: "id")
        .set(source: mySource)
        .build()

client.index(indexRequest, completionHandler: indexHandler)

// get document
func getHandler(_ result: Result<GetResponse<MyClass>, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

let getRequest = try GetRequestBuilder()
    .set(id: "id")
    .set(index: "indexName")
    .set(type: "type")
    .build()

client.get(getRequest, completionHandler: getHandler)

// delete document
func deleteHandler(_ result: Result<DeleteResponse, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

let deleteRequest = try DeleteRequestBuilder()
    .set(index: "indexName")
    .set(type: "type")
    .set(id: "id")
    .build()

client.delete(deleteRequest, completionHandler: deleteHandler)

Query

Currently not all QueryBuilders are available. Future releases will add support for additional QueryBuilders. Check below for details

let builder = QueryBuilders.boolQuery()
let mustMatch = try QueryBuilders.matchQuery().set(field: "fieldName").set(value: "value").build()
let mustNotMatch = try QueryBuilders.matchQuery().set(field: "someFieldName").set(value: "value").build()
builder.must(query: mustMatch)
builder.mustNot(query: mustNotMatch)
let boolQuery = try builder.build()

Search

Creating simple search request.

func handler(_ result: Result<SearchResponse<Message>, Error>) -> Void {
    switch result {
        case .failure(let error):
            print("Error", error)
        case .success(let response):
            print("Response", response)
    }
}

let queryBuilder = QueryBuilders.boolQuery()
let match = try QueryBuilders.matchQuery().set(field: "msg").set(value: "Message").build() 
queryBuilder.must(query: match)

let query =  try queryBuilder.build()

let sort =  SortBuilders.fieldSort("msg") // use "msg.keyword" as field name in case of text field
    .set(order: .asc)
    .build()

let request = try SearchRequestBuilder()
        .set(indices: "indexName")
        .set(types: "type")
        .set(query: query)
        .add(sort: sort)
        .build()

client.search(request, completionHandler: handler)

QueryDSL

Below Table lists all the available search queries with their corresponding QueryBuilder class name and helper method name in the QueryBuilders utility class.

Search Query QueryBuilder Class Method in QueryBuilders
ConstantScoreQuery ConstantScoreQueryBuilder QueryBuilders.constantScoreQuery()
BoolQuery BoolQueryBuilder QueryBuilders.boolQuery()
DisMaxQuery DisMaxQueryBuilder QueryBuilders.disMaxQuery()
FunctionScoreQuery FunctionScoreQueryBuilder QueryBuilders.functionScoreQuery()
BoostingQuery BoostingQueryBuilder QueryBuilders.boostingeQuery()
MatchQuery MatchQueryBuilder QueryBuilders.matchQuery()
MatchPhraseQuery MatchPhraseQueryBuilder QueryBuilders.matchPhraseQuery()
MatchPhrasePrefixQuery MatchPhrasePrefixQueryBuilder QueryBuilders.matchPhrasePrefixQuery()
MultiMatchQuery MultiMatchQueryBuilder QueryBuilders.multiMatchQuery()
CommonTermsQuery CommonTermsQueryBuilder QueryBuilders.commonTermsQuery()
QueryStringQuery QueryStringQueryBuilder QueryBuilders.queryStringQuery()
SimpleQueryStringQuery SimpleQueryStringQueryBuilder QueryBuilders.simpleQueryStringQuery()
MatchAllQuery MatchAllQueryBuilder QueryBuilders.matchAllQuery()
MatchNoneQuery MatchNoneQueryBuilder QueryBuilders.matchNoneQuery()
TermQuery TermQueryBuilder QueryBuilders.termQuery()
TermsQuery TermsQueryBuilder QueryBuilders.termsQuery()
RangeQuery RangeQueryBuilder QueryBuilders.rangeQuery()
ExistsQuery ExistsQueryBuilder QueryBuilders.existsQuery()
PrefixQuery PrefixQueryBuilder QueryBuilders.prefixQuery()
WildCardQuery WildCardQueryBuilder QueryBuilders.wildCardQuery()
RegexpQuery RegexpQueryBuilder QueryBuilders.regexpQuery()
FuzzyQuery FuzzyQueryBuilder QueryBuilders.fuzzyQuery()
TypeQuery TypeQueryBuilder QueryBuilders.typeQuery()
IdsQuery IdsQueryBuilder QueryBuilders.idsQuery()
NestedQuery NestedQueryBuilder QueryBuilders.nestedQuery()
HasChildQuery HasChildQueryBuilder QueryBuilders.hasChildQuery()
HasParentQuery HasParentQueryBuilder QueryBuilders.hasParentQuery()
ParentIdQuery ParentIdQueryBuilder QueryBuilders.parentIdQuery()
GeoShapeQuery GeoShapeQueryBuilder QueryBuilders.geoShapeQuery()
GeoBoundingBoxQuery GeoBoundingBoxQueryBuilder QueryBuilders.geoBoundingBoxQuery()
GeoDistanceQuery GeoDistanceQueryBuilder QueryBuilders.geoDistanceQuery()
GeoPolygonQuery GeoPolygonQueryBuilder QueryBuilders.geoPolygonQuery()
MoreLikeThisQuery MoreLikeThisQueryBuilder QueryBuilders.moreLikeThisQuery()
ScriptQuery ScriptQueryBuilder QueryBuilders.scriptQuery()
PercolateQuery PercoloteQueryBuilder QueryBuilders.percolateQuery()
WrapperQuery WrapperQueryBuilder QueryBuilders.wrapperQuery()
SpanTermQuery SpanTermQueryBuilder QueryBuilders.spanTermQuery()
SpanMultiTermQuery SpanMultiTermQueryBuilder sQueryBuilders.panMultiTermQueryBuilder()
SpanFirstQuery SpanFirstQueryBuilder QueryBuilders.spanFirstQuery()
SpanNearQuery SpanNearQueryBuilder sQueryBuilders.panNearQuery()
SpanOrQuery SpanOrQueryBuilder QueryBuilders.spanOrQuery()
SpanNotQuery SpanNotQueryBuilder QueryBuilders.spanNotQuery()
SpanContainingQuery SpanContainingQueryBuilder QueryBuilders.spanContainingQuery()
SpanWithinQuery SpanWithinQueryBuilder QueryBuilders.spanWithinQuery()
SpanFieldMaskingQueryBuilder SpanFieldMaskingQueryBuilder QueryBuilders.fieldMaskingSpanQuery()

elasticswift's People

Contributors

prafsoni 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

Watchers

 avatar  avatar  avatar  avatar

elasticswift's Issues

Remove no longer necessary toDic() method

Since, Query, ScoreFunction, and Sort conform to Codable Protocol the toDic() which was primarily introduced to ease serialization of class/struct with JSONSerialization in pre-Codable.

It will be good time to remove toDic() before beta/1.0.0

Readme examples and ExampleRepo are not updated

Are there requirements for this to work beyond what's in the readme? I've tried the code samples you supply but there are numerous errors throughout. Especially in the Search section, .match doesn't exist.

GetIndexResponse IndexSettings creationDate is returned as String (not Date)

The response of GetIndexRequest returns a wrong data type for Index settings field creation_date and the deserialization fails.

{ "settings": { "index": {
"creation_date": "1563813426580",
"number_of_shards": "1", "number_of_replicas": "1", "uuid": "N-yD3XmlQd28E9iR4iIliA", "version": { "created": "7000199" },

It's really weird but Elastic returns creation_date as String containing a date in epoch format (seconds from 1970).

Add Contribution Guidelines

Project currently lacks official contribution guidelines. Which is not good for contributors hindering them to create pull request in the desired manner.

Add missing features for SearchRequest

The following features are missing from the search request.

  • Support for multiple Sort
  • Source filtering
  • Stored Fields
  • Scripted Fields
  • track_scores supports
  • Doc Value Fields
  • post_filter
  • highlighting
  • Rescoring
  • Search Type
  • Scroll
  • Preference
  • version
  • seq_no_primary_term
  • indices_boost
  • Named Query (Added support to include matched_queries in response. However, Setting _name for query is a change in supported Queries)
  • inner_hits
  • Field collapsing
  • search after

https://www.elastic.co/guide/en/elasticsearch/reference/6.8/search-request-body.html

SearchResponse broken in alpha.8

Since aplha.8 SearchResponse was returning hits are an empty array. This is due to refactoring SearchHit to make all members constants.

GetIndexRequest - API result is not parsed properly

In the GetIndexRequest method
func responseHandler(_ response: ESResponse) -> Void
deserializing the response in
let decoded: GetIndexResponse? = try Serializers.decode(data: response.data!)
always throw an keyNotFound exception.

This because it expect to find a GetIndexResponse object in the JSON result with following attributes:

  • aliases
  • mappings
  • settings

instead ElasticSearch JSON wraps this object inside an attribute with the name of the index retrieved.

Expected JSON:
{"aliases":{},"mappings":{},"settings":{"index":{"creation_date":"1559206573432","number_of_shards":"1","number_of_replicas":"1","uuid":"I6Ep6P6FQI2wplQCckfr6Q","version":{"created":"7000199"},"provided_name":"test"}}}

Actual JSON:
{"test":
{"aliases":{},"mappings":{},"settings":{"index":{"creation_date":"1559206573432","number_of_shards":"1","number_of_replicas":"1","uuid":"I6Ep6P6FQI2wplQCckfr6Q","version":{"created":"7000199"},"provided_name":"test"}}}
}

ElasticsearchError(error: "Routing Error. The path you have requested is invalid.", status: nil)

Hi, I am trying to use elastic search.
But getting this error

status: NIOHTTP1.HTTPResponseStatus.notFound

ElasticsearchError(error: "Routing Error. The path you have requested is invalid.", status: nil)

My code is below (pretty much copied from readme).

func makeClient() -> ElasticClient {
    let host = "<my endpoint is on here>"
    
    let cred = BasicClientCredential(username: "elastic", password: "<password here>")
    let settings = Settings(forHost: host,
                            withCredentials: cred,
                            adaptorConfig: URLSessionAdaptorConfiguration.default)
    return ElasticClient(settings: settings)
}
func searchRequest() {
    
    func handler(_ result: Result<SearchResponse<Sauna>, Error>) -> Void {
        switch result {
        case .failure(let error):
            print("searchRequest.Error", error) // getting error here
        case .success(let response):
            print("searchRequest.Response", response)
        }
    }
   
    client = makeClient()
    
    do {
        
        let queryBuilder = QueryBuilders.boolQuery()
        let match = try QueryBuilders.matchQuery()
                .set(field: "saunas")
                .set(value: "Sauna")
                .build()
        queryBuilder.must(query: match)

        let query = try queryBuilder.build()

        let sort = SortBuilders.fieldSort("saunas")
                .set(order: .asc)
                .build()

        let request = try SearchRequestBuilder()
                .set(indices: "indexName")
                .set(types: "type")
                .set(query: query)
                .add(sort: sort)
                .build()

        client.search(request, completionHandler: handler)
    } catch {
        print("searchRequest.error", error)
    }
}

I have Engines named "saunas" on elastic which contains 89 documents.

I have no idea what "indices" or "types" means in the SearchRequestBuilder.
Is there any documents that explaining those basic things?

I am pretty new to elastic, so sorry for the noob questions.. but I've been struggling for weeks.
Really appreciate your help!!!

Have you considered using Futures instead of callbacks?

This would be similar to how Fluent works in Vapor and avoid callbacks.

I could see something along these lines:

return client.query().set(indices: ...).set(types: ...).search().map {
       // Code
}

Where .search() returns an EventLoopFuture<SearchResponse<T: Codable>>

It would make the code much more concise (Swifty 😉) and would fit better with packages like Vapor and Soto.

Builders should have a build() method

Currently, QueryBuilder has a query property that returns build query. This should really be a build(). Don't remember why it was implemented this way, this needs to be corrected.

Please don't raise target platform requirements

I see you are experimenting new libraries.
I kindly ask you to keep compatibility with at least the latest 3 iOS versions (at the moment 10 11 12)
Another recommendation is to avoid 3rd party libraries as much as possible, i.e. avoid Alamofire or other networking libs that are not maintained by Apple if they don't generate really great huge benefits, it would be a pain to maintain on OS upgrade.

Thanks for the great work you are doing!

Inconsistency in Enum case naming

Most of the enum cases are named in camel case but some are either snake and/or upper cased.
Naming should be consistent across packages. Since majority of them are camel-cased. Using the camel case as standard will be require minimum change.

Not possible to use server on HTTPS with a valid public certificate

Describe the bug
On iOS (and possibly macOS) The custom self-signed TLS certificate support doesn't let you use a valid public certificate from—for example—Let's Encrypt.

To Reproduce
Trying to use the example from the documentation to make a basic query on a server that is using a valid public TLS certificate on iOS:

import ElasticSwift
import ElasticSwiftNetworking

var settings = Settings(forHost: "https://www.example.com", adaptorConfig: URLSessionAdaptorConfiguration.default) // Creates default settings for client
var client = ElasticClient(settings: settings) // Creates client with specified settings

This causes a relatively opaque error message:

Error Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://www.example.com/my-index/_search?, NSErrorFailingURLKey=https://www.example.com/my-index/_search?, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <CD9D4F4D-A6F9-4BA9-8B29-E75A486DFA3E>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <CD9D4F4D-A6F9-4BA9-8B29-E75A486DFA3E>.<1>, NSLocalizedDescription=cancelled}

Expected behavior
I expect that supporting a self-signed certificate requires configuration but supporting a public trusted certificate works by default. The NIO adapter seems to work by default.

Environment Details (please complete the following information):

  • OS: iOS
  • Version iOS 14, Xcode 12

ElasticSwift (please complete the following information):

  • Version 1.0.0-beta.1

Additional context
It looks to me like the code in https://github.com/pksprojects/ElasticSwift/blob/master/Sources/ElasticSwiftNetworking/SessionManager.swift#L94-L96 is what causes the -999 error to occur. I'm not familiar enough with custom TLS handling in URLSession to be able to say for sure, but should this call the completion handler with .performDefaultHandling instead of .cancelAuthenticationChallenge?

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.