An Elasticsearch client for Go (original) (raw)

Introduction

Elastic is a client forElasticsearch for theGo programming language. We use it in production since 2012. It supports Elasticsearch versions 1.x, 2.x, 5.x, 6.x and 7.x.

The code is MIT licensed and hosted onGitHub. Pleasereport issues on GitHub. If you want to have a deeper look and are searching for more examples, thewiki on GitHub is a good place to go.

Versions

Elastic supports different versions of Elasticsearch. However, you must choose the version of Elastic that matches the Elasticsearch version.

Elasticsearch version Elastic version Package URL
7.x 7.0 https://github.com/olivere/elastic/v7 (sourcedocschangelog)
6.x 6.0 https://github.com/olivere/elastic (sourcedocschangelog)
5.x 5.0 https://gopkg.in/olivere/elastic.v5 (sourcedocschangelog)
2.x 3.0 https://gopkg.in/olivere/elastic.v3 (sourcedocschangelog)
1.x 2.0 https://gopkg.in/olivere/elastic.v2 (sourcedocs)
0.9-1.3 1.0 https://gopkg.in/olivere/elastic.v1 (sourcedocs)

Example: You have Elasticsearch 5.0.0 installed and want to use Elastic. As listed above, you should use Elastic 5.0. So you first install Elastic 5.0 with:

go get gopkg.in/olivere/elastic.v5

You the use the following import path in your Go code:

import "gopkg.in/olivere/elastic.v5"
Notice

The GitHub repository athttps://github.com/olivere/elastic always refers to the latest version of Elastic. If you want to use stable versions of Elastic, you should use a tagged version.

Starting with Elastic 7.0, we support Go modules. You must use an import path ofgithub.com/olivere/elastic/v7. Notice that you also must use a version of Go that is compatible with Go modules (Go 1.10.3 or later).

With Elastic 6.0 we started tagging releases directly ongithub.com/olivere/elastic. So with Elastic 6.x forward, you should also be able to usedep. Notice however that dep still lacks support for version numbers in import paths (see PR #1963).

Earlier versions of Elastic (5.x and earlier) published packages via gopkg.in as described above.

Getting Started

The following code targets Elasticsearch 6.x and uses Elastic 6.0. To use Elastic 6.0 in your project, first go get it:

go get github.com/olivere/elastic

Then start using it in your code. Here's a complete example:

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "reflect"
    "time"

    "github.com/olivere/elastic"
)

// Tweet is a structure used for serializing/deserializing data in Elasticsearch.
type Tweet struct {
    User     string                `json:"user"`
    Message  string                `json:"message"`
    Retweets int                   `json:"retweets"`
    Image    string                `json:"image,omitempty"`
    Created  time.Time             `json:"created,omitempty"`
    Tags     []string              `json:"tags,omitempty"`
    Location string                `json:"location,omitempty"`
    Suggest  *elastic.SuggestField `json:"suggest_field,omitempty"`
}

const mapping = `
{
    "settings":{
        "number_of_shards": 1,
        "number_of_replicas": 0
    },
    "mappings":{
        "tweet":{
            "properties":{
                "user":{
                    "type":"keyword"
                },
                "message":{
                    "type":"text",
                    "store": true,
                    "fielddata": true
                },
                "image":{
                    "type":"keyword"
                },
                "created":{
                    "type":"date"
                },
                "tags":{
                    "type":"keyword"
                },
                "location":{
                    "type":"geo_point"
                },
                "suggest_field":{
                    "type":"completion"
                }
            }
        }
    }
}`

func main() {
    // Starting with elastic.v5, you must pass a context to execute each service
    ctx := context.Background()

    // Obtain a client and connect to the default Elasticsearch installation
    // on 127.0.0.1:9200. Of course you can configure your client to connect
    // to other hosts and configure it in various other ways.
    client, err := elastic.NewClient()
    if err != nil {
        // Handle error
        panic(err)
    }

    // Ping the Elasticsearch server to get e.g. the version number
    info, code, err := client.Ping("http://127.0.0.1:9200").Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    fmt.Printf("Elasticsearch returned with code %d and version %s\n", code, info.Version.Number)

    // Getting the ES version number is quite common, so there's a shortcut
    esversion, err := client.ElasticsearchVersion("http://127.0.0.1:9200")
    if err != nil {
        // Handle error
        panic(err)
    }
    fmt.Printf("Elasticsearch version %s\n", esversion)

    // Use the IndexExists service to check if a specified index exists.
    exists, err := client.IndexExists("twitter").Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    if !exists {
        // Create a new index.
        createIndex, err := client.CreateIndex("twitter").BodyString(mapping).Do(ctx)
        if err != nil {
            // Handle error
            panic(err)
        }
        if !createIndex.Acknowledged {
            // Not acknowledged
        }
    }

    // Index a tweet (using JSON serialization)
    tweet1 := Tweet{User: "olivere", Message: "Take Five", Retweets: 0}
    put1, err := client.Index().
        Index("twitter").
        Type("tweet").
        Id("1").
        BodyJson(tweet1).
        Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    fmt.Printf("Indexed tweet %s to index %s, type %s\n", put1.Id, put1.Index, put1.Type)

    // Index a second tweet (by string)
    tweet2 := `{"user" : "olivere", "message" : "It's a Raggy Waltz"}`
    put2, err := client.Index().
        Index("twitter").
        Type("tweet").
        Id("2").
        BodyString(tweet2).
        Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    fmt.Printf("Indexed tweet %s to index %s, type %s\n", put2.Id, put2.Index, put2.Type)

    // Get tweet with specified ID
    get1, err := client.Get().
        Index("twitter").
        Type("tweet").
        Id("1").
        Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    if get1.Found {
        fmt.Printf("Got document %s in version %d from index %s, type %s\n", get1.Id, get1.Version, get1.Index, get1.Type)
    }

    // Flush to make sure the documents got written.
    _, err = client.Flush().Index("twitter").Do(ctx)
    if err != nil {
        panic(err)
    }

    // Search with a term query
    termQuery := elastic.NewTermQuery("user", "olivere")
    searchResult, err := client.Search().
        Index("twitter").   // search in index "twitter"
        Query(termQuery).   // specify the query
        Sort("user", true). // sort by "user" field, ascending
        From(0).Size(10).   // take documents 0-9
        Pretty(true).       // pretty print request and response JSON
        Do(ctx)             // execute
    if err != nil {
        // Handle error
        panic(err)
    }

    // searchResult is of type SearchResult and returns hits, suggestions,
    // and all kinds of other information from Elasticsearch.
    fmt.Printf("Query took %d milliseconds\n", searchResult.TookInMillis)

    // Each is a convenience function that iterates over hits in a search result.
    // It makes sure you don't need to check for nil values in the response.
    // However, it ignores errors in serialization. If you want full control
    // over iterating the hits, see below.
    var ttyp Tweet
    for _, item := range searchResult.Each(reflect.TypeOf(ttyp)) {
        if t, ok := item.(Tweet); ok {
            fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
        }
    }
    // TotalHits is another convenience function that works even when something goes wrong.
    fmt.Printf("Found a total of %d tweets\n", searchResult.TotalHits())

    // Here's how you iterate through results with full control over each step.
    if searchResult.Hits.TotalHits > 0 {
        fmt.Printf("Found a total of %d tweets\n", searchResult.Hits.TotalHits)

        // Iterate through results
        for _, hit := range searchResult.Hits.Hits {
            // hit.Index contains the name of the index

            // Deserialize hit.Source into a Tweet (could also be just a map[string]interface{}).
            var t Tweet
            err := json.Unmarshal(*hit.Source, &t)
            if err != nil {
                // Deserialization failed
            }

            // Work with tweet
            fmt.Printf("Tweet by %s: %s\n", t.User, t.Message)
        }
    } else {
        // No hits
        fmt.Print("Found no tweets\n")
    }

    // Update a tweet by the update API of Elasticsearch.
    // We just increment the number of retweets.
    update, err := client.Update().Index("twitter").Type("tweet").Id("1").
        Script(elastic.NewScriptInline("ctx._source.retweets += params.num").Lang("painless").Param("num", 1)).
        Upsert(map[string]interface{}{"retweets": 0}).
        Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    fmt.Printf("New version of tweet %q is now %d\n", update.Id, update.Version)

    // ...

    // Delete an index.
    deleteIndex, err := client.DeleteIndex("twitter").Do(ctx)
    if err != nil {
        // Handle error
        panic(err)
    }
    if !deleteIndex.Acknowledged {
        // Not acknowledged
    }
}