datastore.go 3.05 KB
Newer Older
1 2 3 4
package elastigo

import (
	"fmt"
5
	"github.com/codahale/blake2"
6 7 8
	ds "github.com/jbenet/datastore.go"
	"github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
9 10
	"net/url"
	"strings"
11 12 13 14 15 16 17 18
)

// Currently, elastigo does not allow connecting to multiple elasticsearch
// instances. The elastigo API uses global static variables (ugh).
// See https://github.com/mattbaird/elastigo/issues/22
//
// Thus, we use a global static variable (GlobalInstance), and return an
// error if NewDatastore is called twice with different addresses.
19
var GlobalInstance string
20 21 22

// Datastore uses a standard Go map for internal storage.
type Datastore struct {
23
	url   string
24
	index string
25 26 27 28

	// Elastic search does not allow slashes in their object ids,
	// so we hash the key. By default, we use the provided BlakeKeyHash
	KeyHash func(ds.Key) string
29 30
}

31 32
func NewDatastore(urlstr string) (*Datastore, error) {
	if GlobalInstance != "" && GlobalInstance != urlstr {
33 34 35
		return nil, fmt.Errorf("elastigo only allows one client. See godoc.")
	}

36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
	uf := "http://<host>:<port>/<index>"
	u, err := url.Parse(urlstr)
	if err != nil {
		return nil, fmt.Errorf("error parsing url: %s (%s)", urlstr, uf)
	}

	host := strings.Split(u.Host, ":")
	api.Domain = host[0]
	if len(host) > 1 {
		api.Port = host[1]
	}

	index := strings.Trim(u.Path, "/")
	if strings.Contains(index, "/") {
		e := "elastigo index cannot have slashes: %s (%s -> %s)"
		return nil, fmt.Errorf(e, index, urlstr, uf)
52 53
	}

54
	GlobalInstance = urlstr
55
	return &Datastore{
56
		url:     urlstr,
57 58
		index:   index,
		KeyHash: BlakeKeyHash,
59 60 61 62 63 64 65 66 67 68 69 70 71 72
	}, nil
}

// Returns the ElasticSearch index for given key. If the datastore specifies
// an index, use that. Else, key.Parent
func (d *Datastore) Index(key ds.Key) string {
	if len(d.index) > 0 {
		return d.index
	}
	return key.Parent().BaseNamespace()
}

// value should be JSON serializable.
func (d *Datastore) Put(key ds.Key, value interface{}) (err error) {
73 74
	id := d.KeyHash(key)
	res, err := core.Index(false, d.Index(key), key.Type(), id, value)
75 76 77 78 79 80 81 82 83 84
	if err != nil {
		return err
	}
	if !res.Ok {
		return fmt.Errorf("Elasticsearch response: NOT OK. %v", res)
	}
	return nil
}

func (d *Datastore) Get(key ds.Key) (value interface{}, err error) {
85 86
	id := d.KeyHash(key)
	res, err := core.Get(false, d.Index(key), key.Type(), id)
87 88 89 90 91 92 93 94 95 96
	if err != nil {
		return nil, err
	}
	if !res.Ok {
		return nil, fmt.Errorf("Elasticsearch response: NOT OK. %v", res)
	}
	return res.Source, nil
}

func (d *Datastore) Has(key ds.Key) (exists bool, err error) {
97 98
	id := d.KeyHash(key)
	return core.Exists(false, d.Index(key), key.Type(), id)
99 100 101
}

func (d *Datastore) Delete(key ds.Key) (err error) {
102 103
	id := d.KeyHash(key)
	res, err := core.Delete(false, d.Index(key), key.Type(), id, 0, "")
104 105 106 107 108 109 110 111
	if err != nil {
		return err
	}
	if !res.Ok {
		return fmt.Errorf("Elasticsearch response: NOT OK. %v", res)
	}
	return nil
}
112 113 114 115 116 117 118 119 120

// Hash a key and return the first 16 hex chars of its blake2b hash.
// basically: Blake2b(key).HexString[:16]
func BlakeKeyHash(key ds.Key) string {
	h := blake2.NewBlake2B()
	h.Write(key.Bytes())
	d := h.Sum(nil)
	return fmt.Sprintf("%x", d)[:16]
}