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

import (
	"fmt"
5
	"github.com/codahale/blake2"
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
	ds "github.com/jbenet/datastore.go"
	"github.com/mattbaird/elastigo/api"
	"github.com/mattbaird/elastigo/core"
)

// 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.
var GlobalInstance Address

type Address struct {
	Host string
	Port int
}

// Datastore uses a standard Go map for internal storage.
type Datastore struct {
	addr  Address
	index string
28 29 30 31

	// 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
32 33 34 35 36 37 38 39 40 41 42 43 44 45
}

func NewDatastore(addr Address, index string) (*Datastore, error) {
	if GlobalInstance.Host != "" && GlobalInstance != addr {
		return nil, fmt.Errorf("elastigo only allows one client. See godoc.")
	}

	api.Domain = addr.Host
	if addr.Port > 0 {
		api.Port = string(addr.Port)
	}

	GlobalInstance = addr
	return &Datastore{
46 47 48
		addr:    addr,
		index:   index,
		KeyHash: BlakeKeyHash,
49 50 51 52 53 54 55 56 57 58 59 60 61 62
	}, 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) {
63 64
	id := d.KeyHash(key)
	res, err := core.Index(false, d.Index(key), key.Type(), id, value)
65 66 67 68 69 70 71 72 73 74
	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) {
75 76
	id := d.KeyHash(key)
	res, err := core.Get(false, d.Index(key), key.Type(), id)
77 78 79 80 81 82 83 84 85 86
	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) {
87 88
	id := d.KeyHash(key)
	return core.Exists(false, d.Index(key), key.Type(), id)
89 90 91
}

func (d *Datastore) Delete(key ds.Key) (err error) {
92 93
	id := d.KeyHash(key)
	res, err := core.Delete(false, d.Index(key), key.Type(), id, 0, "")
94 95 96 97 98 99 100 101
	if err != nil {
		return err
	}
	if !res.Ok {
		return fmt.Errorf("Elasticsearch response: NOT OK. %v", res)
	}
	return nil
}
102 103 104 105 106 107 108 109 110

// 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]
}