├── .gitignore ├── README.md ├── bucket.go ├── bucket_where.go ├── codec ├── codec.go ├── gob │ └── gob.go ├── json │ └── json.go ├── proto │ └── proto.go └── string │ └── string.go ├── cursor.go ├── db.go ├── errors └── errors.go ├── testdata └── .gitignore ├── thunder.go ├── thunder_test.go └── tx.go /.gitignore: -------------------------------------------------------------------------------- 1 | testdata/* 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thunder 2 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/omeid/thunder) ![Project status](https://img.shields.io/badge/status-experimental-red.svg?style=flat-square) 3 | 4 | Thunder is a high productivity wrapper for [BoltDB](https://github.com/boltdb/bolt). 5 | The idea behind thunder is to allow users to rapidly develop but allow them to fallback to using BoltDB directly by maintaining an almost functionally identical API yet hide some of the complexity involved around using a serializaiton format, filtering, batch insert and reads. 6 | 7 | 8 | Thunder stays unopinionated about serialization format used by allowing users to bring their own [Codec](https://godoc.org/github.com/omeid/thunder/codec) by implementing a simple interface: 9 | 10 | ```go 11 | type Codec interface { 12 | Marshaler(v interface{}) encoding.BinaryMarshaler 13 | Unmarshaler(v interface{}) encoding.BinaryUnmarshaler 14 | } 15 | ``` 16 | 17 | Thunder comes with `json`, `string`, `gob`, and `Protocol Buffers` codecs for free. You may mix and match any combination of Codecs for your keys and values. 18 | -------------------------------------------------------------------------------- /bucket.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/boltdb/bolt" 8 | "github.com/omeid/thunder/codec" 9 | ) 10 | 11 | type Bucket struct { 12 | bucket *bolt.Bucket 13 | 14 | kc codec.Codec 15 | vc codec.Codec 16 | 17 | err error 18 | } 19 | 20 | func (b *Bucket) Err() error { 21 | return b.err 22 | } 23 | 24 | func (b *Bucket) NextSequence() (uint64, error) { 25 | return b.bucket.NextSequence() 26 | } 27 | func (b *Bucket) Bucket(name interface{}) *Bucket { 28 | 29 | if b.err != nil { 30 | return &Bucket{nil, b.kc, b.vc, b.err} 31 | } 32 | 33 | n, err := b.kc.Marshaler(name).MarshalBinary() 34 | if err != nil { 35 | return &Bucket{nil, b.kc, b.vc, err} 36 | } 37 | 38 | bucket := b.bucket.Bucket(n) 39 | if bucket == nil { 40 | err = bolt.ErrBucketNotFound 41 | } 42 | 43 | return &Bucket{bucket, b.kc, b.vc, err} 44 | } 45 | 46 | func (b *Bucket) Cursor() *Cursor { 47 | return &Cursor{b.bucket.Cursor(), b.kc, b.vc, b.err} 48 | } 49 | 50 | func (b *Bucket) CreateBucketIfNotExists(name interface{}) *Bucket { 51 | if b.err != nil { 52 | return &Bucket{nil, b.kc, b.vc, b.err} 53 | } 54 | 55 | n, err := b.kc.Marshaler(name).MarshalBinary() 56 | if err != nil { 57 | return &Bucket{nil, b.kc, b.vc, err} 58 | } 59 | 60 | bucket, err := b.bucket.CreateBucketIfNotExists(n) 61 | return &Bucket{ 62 | bucket: bucket, 63 | kc: b.kc, 64 | vc: b.vc, 65 | err: err, 66 | } 67 | 68 | } 69 | 70 | func (b *Bucket) Put(key interface{}, value interface{}) error { 71 | if b.err != nil { 72 | return b.err 73 | } 74 | k, err := b.kc.Marshaler(key).MarshalBinary() 75 | if err != nil { 76 | return err 77 | } 78 | v, err := b.vc.Marshaler(value).MarshalBinary() 79 | if err != nil { 80 | return err 81 | } 82 | return b.bucket.Put(k, v) 83 | } 84 | 85 | func (b *Bucket) Get(key interface{}, value interface{}) error { 86 | if b.err != nil { 87 | return b.err 88 | } 89 | 90 | k, err := b.kc.Marshaler(key).MarshalBinary() 91 | if err != nil { 92 | return err 93 | } 94 | v := b.bucket.Get(k) 95 | 96 | return b.vc.Unmarshaler(value).UnmarshalBinary(v) 97 | } 98 | 99 | func (b *Bucket) Insert(kvm interface{}) error { 100 | 101 | if b.err != nil { 102 | return b.err 103 | } 104 | mv := reflect.Indirect(reflect.ValueOf(kvm)) 105 | 106 | if mv.Kind() != reflect.Map { 107 | return fmt.Errorf("Thunder: Bucket.Insert Expects a Map") 108 | } 109 | 110 | if mv.IsNil() { 111 | return nil //Nothing to do. There is no data. 112 | } 113 | 114 | var err error 115 | for _, key := range mv.MapKeys() { 116 | value := mv.MapIndex(key) 117 | err = b.Put(key.Interface(), value.Interface()) 118 | if err != nil { 119 | return err 120 | } 121 | } 122 | 123 | return nil 124 | } 125 | 126 | func (b *Bucket) All(kvm interface{}) error { 127 | return b.Where(func(interface{}) bool { return true }, kvm) 128 | } 129 | -------------------------------------------------------------------------------- /bucket_where.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | 7 | "github.com/omeid/thunder/codec" 8 | ) 9 | 10 | type setter func(k, v []byte) error 11 | type Mattcher func(interface{}) bool 12 | 13 | func mapSetter(kc, vc codec.Codec, mv reflect.Value, match Mattcher) (setter, error) { 14 | 15 | mv = reflect.Indirect(mv) 16 | mvt := mv.Type() 17 | kt := mvt.Key() 18 | vt := mvt.Elem() 19 | 20 | if mv.IsNil() { 21 | return nil, fmt.Errorf("Thunder: Bucket.Where expects non nil map.") 22 | } 23 | 24 | return func(k, v []byte) error { 25 | 26 | key, value := reflect.New(kt), reflect.New(vt) 27 | err := unmarshal(kc, vc, k, v, key.Interface(), value.Interface()) 28 | if err != nil { 29 | return err 30 | } 31 | if kt.Kind() != reflect.Ptr { 32 | key = reflect.Indirect(key) 33 | } 34 | if vt.Kind() != reflect.Ptr { 35 | value = reflect.Indirect(value) 36 | } 37 | 38 | if match(value.Interface()) { 39 | mv.SetMapIndex(key, value) 40 | } 41 | return nil 42 | }, nil 43 | } 44 | 45 | func sliceSetter(kc, vc codec.Codec, svp reflect.Value, match Mattcher) (setter, error) { 46 | 47 | //svp: Slice Value Pointer 48 | //sv Slice Value 49 | //vt Value Type 50 | 51 | if svp.Kind() != reflect.Ptr { 52 | return nil, fmt.Errorf("Thunder:Where Expects Slice to be a pointer. %s", svp.Kind()) 53 | } 54 | 55 | sv := svp.Elem() 56 | vt := sv.Type().Elem() 57 | return func(k, v []byte) error { 58 | value := reflect.New(vt) 59 | 60 | err := vc.Unmarshaler(value.Interface()).UnmarshalBinary(v) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | if vt.Kind() != reflect.Ptr { 66 | value = reflect.Indirect(value) 67 | } 68 | 69 | if match(value.Interface()) { 70 | sv.Set(reflect.Append(sv, value)) 71 | } 72 | return nil 73 | }, nil 74 | } 75 | 76 | func (b *Bucket) Where(match Mattcher, kvm interface{}) error { 77 | 78 | if b.err != nil { 79 | return b.err 80 | } 81 | 82 | mv := reflect.ValueOf(kvm) 83 | 84 | var ( 85 | apply setter 86 | err error 87 | ) 88 | 89 | switch mv.Type().Elem().Kind() { 90 | case reflect.Map: 91 | apply, err = mapSetter(b.kc, b.vc, mv, match) 92 | case reflect.Slice: 93 | apply, err = sliceSetter(b.kc, b.vc, mv, match) 94 | default: 95 | err = fmt.Errorf("Thunder:Where Expects a Map or Slice. %T given.", kvm) 96 | } 97 | 98 | if err != nil { 99 | return err 100 | } 101 | return b.bucket.ForEach(func(k, v []byte) error { 102 | return apply(k, v) 103 | 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /codec/codec.go: -------------------------------------------------------------------------------- 1 | package codec 2 | 3 | import "encoding" 4 | 5 | type Codec interface { 6 | Marshaler(v interface{}) encoding.BinaryMarshaler 7 | Unmarshaler(v interface{}) encoding.BinaryUnmarshaler 8 | } 9 | -------------------------------------------------------------------------------- /codec/gob/gob.go: -------------------------------------------------------------------------------- 1 | package gob 2 | 3 | import ( 4 | "bytes" 5 | "encoding" 6 | "encoding/gob" 7 | 8 | "github.com/omeid/thunder/codec" 9 | ) 10 | 11 | func Codec() codec.Codec { 12 | return gobCodec{} 13 | } 14 | 15 | type marshaler struct { 16 | value interface{} 17 | } 18 | 19 | func (m marshaler) MarshalBinary() ([]byte, error) { 20 | var buff *bytes.Buffer 21 | err := gob.NewEncoder(buff).Encode(m.value) 22 | return buff.Bytes(), err 23 | } 24 | 25 | type unmarshaler struct { 26 | value interface{} 27 | } 28 | 29 | func (m unmarshaler) UnmarshalBinary(data []byte) error { 30 | return gob.NewDecoder(bytes.NewReader(data)).Decode(m.value) 31 | } 32 | 33 | type gobCodec struct{} 34 | 35 | func (c gobCodec) Marshaler(v interface{}) encoding.BinaryMarshaler { 36 | return marshaler{v} 37 | } 38 | 39 | func (c gobCodec) Unmarshaler(v interface{}) encoding.BinaryUnmarshaler { 40 | return unmarshaler{v} 41 | } 42 | -------------------------------------------------------------------------------- /codec/json/json.go: -------------------------------------------------------------------------------- 1 | package json 2 | 3 | import ( 4 | "encoding" 5 | "encoding/json" 6 | 7 | "github.com/omeid/thunder/codec" 8 | ) 9 | 10 | func Codec() codec.Codec { 11 | return jsonCodec{} 12 | } 13 | 14 | type marshaler struct { 15 | value interface{} 16 | } 17 | 18 | func (m marshaler) MarshalBinary() ([]byte, error) { 19 | return json.Marshal(m.value) 20 | } 21 | 22 | type unmarshaler struct { 23 | value interface{} 24 | } 25 | 26 | func (m unmarshaler) UnmarshalBinary(data []byte) error { 27 | return json.Unmarshal(data, m.value) 28 | } 29 | 30 | type jsonCodec struct{} 31 | 32 | func (c jsonCodec) Marshaler(v interface{}) encoding.BinaryMarshaler { 33 | return marshaler{v} 34 | } 35 | 36 | func (c jsonCodec) Unmarshaler(v interface{}) encoding.BinaryUnmarshaler { 37 | return unmarshaler{v} 38 | } 39 | -------------------------------------------------------------------------------- /codec/proto/proto.go: -------------------------------------------------------------------------------- 1 | package proto 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | 7 | "github.com/golang/protobuf/proto" 8 | 9 | "github.com/omeid/thunder/codec" 10 | ) 11 | 12 | func Codec() codec.Codec { 13 | return protoCodec{} 14 | } 15 | 16 | type marshaler struct { 17 | value interface{} 18 | } 19 | 20 | func (m marshaler) MarshalBinary() ([]byte, error) { 21 | bm, ok := m.value.(proto.Message) 22 | if !ok { 23 | return nil, fmt.Errorf("Proto Encoder Marshaler expects a proto.Message") 24 | } 25 | return proto.Marshal(bm) 26 | } 27 | 28 | type unmarshaler struct { 29 | value interface{} 30 | } 31 | 32 | func (m unmarshaler) UnmarshalBinary(data []byte) error { 33 | bm, ok := m.value.(proto.Message) 34 | if !ok { 35 | return fmt.Errorf("Proto Encoder Marshaler expects a proto.Message") 36 | } 37 | return proto.Unmarshal(data, bm) 38 | } 39 | 40 | type protoCodec struct{} 41 | 42 | func (c protoCodec) Marshaler(v interface{}) encoding.BinaryMarshaler { 43 | return marshaler{v} 44 | } 45 | 46 | func (c protoCodec) Unmarshaler(v interface{}) encoding.BinaryUnmarshaler { 47 | return unmarshaler{v} 48 | } 49 | -------------------------------------------------------------------------------- /codec/string/string.go: -------------------------------------------------------------------------------- 1 | package strings 2 | 3 | import ( 4 | "encoding" 5 | "fmt" 6 | "reflect" 7 | 8 | "github.com/omeid/thunder/codec" 9 | ) 10 | 11 | func Codec() codec.Codec { 12 | return stringCodec{} 13 | } 14 | 15 | type marshaler struct { 16 | value interface{} 17 | } 18 | 19 | func (m marshaler) MarshalBinary() ([]byte, error) { 20 | 21 | var err error 22 | 23 | v := reflect.Indirect(reflect.ValueOf(m.value)) 24 | if v.Kind() != reflect.String { 25 | err = fmt.Errorf("Strings Codec: Expected type string but got %s", reflect.TypeOf(m.value)) 26 | } 27 | 28 | vs := v.Interface().(string) 29 | if vs == "" { 30 | err = fmt.Errorf("Empty Key.") 31 | } 32 | return []byte(vs), err 33 | } 34 | 35 | type unmarshaler struct { 36 | value interface{} 37 | } 38 | 39 | func (m unmarshaler) UnmarshalBinary(data []byte) error { 40 | 41 | rv := reflect.ValueOf(m.value) 42 | if rv.Kind() != reflect.Ptr || rv.Elem().Kind() != reflect.String { 43 | return fmt.Errorf("Strings Codec: Expects Non-nil String Pointer.") 44 | } 45 | rv.Elem().SetString(string(data)) 46 | return nil 47 | } 48 | 49 | type stringCodec struct{} 50 | 51 | func (c stringCodec) Marshaler(v interface{}) encoding.BinaryMarshaler { 52 | return marshaler{v} 53 | } 54 | 55 | func (c stringCodec) Unmarshaler(v interface{}) encoding.BinaryUnmarshaler { 56 | return unmarshaler{v} 57 | } 58 | -------------------------------------------------------------------------------- /cursor.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "github.com/omeid/thunder/codec" 6 | ) 7 | 8 | func unmarshal(kc, vc codec.Codec, k, v []byte, key, value interface{}) error { 9 | if k == nil && v == nil { 10 | return ErrKeyValueNotFound 11 | } 12 | err := kc.Unmarshaler(key).UnmarshalBinary(k) 13 | if err != nil { 14 | return err 15 | } 16 | 17 | err = vc.Unmarshaler(value).UnmarshalBinary(v) 18 | return err 19 | } 20 | 21 | type Cursor struct { 22 | cursor *bolt.Cursor 23 | 24 | kc codec.Codec 25 | vc codec.Codec 26 | 27 | err error //for bubbling up errors. 28 | } 29 | 30 | func (cur *Cursor) Err() error { 31 | return cur.err 32 | } 33 | 34 | func (cur *Cursor) Bucket() *Bucket { 35 | return &Bucket{cur.cursor.Bucket(), cur.kc, cur.vc, cur.err} 36 | } 37 | 38 | func (cur *Cursor) First(key interface{}, value interface{}) error { 39 | if cur.err != nil { 40 | return cur.err 41 | } 42 | k, v := cur.cursor.First() 43 | return unmarshal(cur.kc, cur.vc, k, v, key, value) 44 | } 45 | 46 | func (cur *Cursor) Last(key interface{}, value interface{}) error { 47 | if cur.err != nil { 48 | return cur.err 49 | } 50 | k, v := cur.cursor.Last() 51 | return unmarshal(cur.kc, cur.vc, k, v, key, value) 52 | } 53 | 54 | func (cur *Cursor) Next(key interface{}, value interface{}) error { 55 | if cur.err != nil { 56 | return cur.err 57 | } 58 | k, v := cur.cursor.Next() 59 | return unmarshal(cur.kc, cur.vc, k, v, key, value) 60 | } 61 | 62 | func (cur *Cursor) Prev(key interface{}, value interface{}) error { 63 | if cur.err != nil { 64 | return cur.err 65 | } 66 | k, v := cur.cursor.Prev() 67 | return unmarshal(cur.kc, cur.vc, k, v, key, value) 68 | } 69 | 70 | func (cur *Cursor) Seek(seek interface{}, key interface{}, value interface{}) error { 71 | if cur.err != nil { 72 | return cur.err 73 | } 74 | s, err := cur.kc.Marshaler(seek).MarshalBinary() 75 | if err != nil { 76 | return err 77 | } 78 | 79 | k, v := cur.cursor.Seek(s) 80 | return unmarshal(cur.kc, cur.vc, k, v, key, value) 81 | } 82 | 83 | func New(db *bolt.DB, KeyCodec, ValueCodec codec.Codec) *DB { 84 | return &DB{db: db, kc: KeyCodec, vc: ValueCodec} 85 | } 86 | -------------------------------------------------------------------------------- /db.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "github.com/omeid/thunder/codec" 6 | ) 7 | 8 | type DB struct { 9 | db *bolt.DB 10 | 11 | kc codec.Codec 12 | vc codec.Codec 13 | } 14 | 15 | func (b *DB) Begin(writeable bool) *Tx { 16 | tx, err := b.db.Begin(writeable) 17 | return &Tx{tx, b.kc, b.vc, err} 18 | } 19 | 20 | func (b *DB) View(fn func(*Tx) error) error { 21 | return b.db.View(func(tx *bolt.Tx) error { 22 | return fn(&Tx{tx, b.kc, b.vc, nil}) 23 | }) 24 | } 25 | 26 | func (b *DB) Update(fn func(*Tx) error) error { 27 | return b.db.Update(func(tx *bolt.Tx) error { 28 | return fn(&Tx{tx, b.kc, b.vc, nil}) 29 | }) 30 | } 31 | 32 | func (db *DB) Sync() error { 33 | return db.db.Sync() 34 | } 35 | 36 | func (db *DB) Close() error { 37 | return db.db.Close() 38 | } 39 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import "fmt" 4 | 5 | // Err is the string representation of an error. It may include formating directives. 6 | type Err string 7 | 8 | // Class defines where an error orginated from. 9 | type Class int 10 | 11 | const ( 12 | Unknown Class = iota 13 | DB 14 | Serializer 15 | Codec 16 | Thunder 17 | ) 18 | 19 | type Error interface { 20 | Class() Class 21 | Err() Err 22 | Error() string 23 | } 24 | 25 | type err struct { 26 | class Class 27 | err Err 28 | details []interface{} 29 | } 30 | 31 | func (e err) Class() Class { 32 | return e.class 33 | } 34 | 35 | func (e err) Err() Err { 36 | return e.err 37 | } 38 | func (e err) Error() string { 39 | return fmt.Sprintf(string(e.err), e.details...) 40 | } 41 | 42 | func New(c Class, e Err, details ...interface{}) Error { 43 | return err{class: c, err: e, details: details} 44 | } 45 | -------------------------------------------------------------------------------- /testdata/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/omeid/thunder/c7eed53057ba72f101c13980895b7adb312e4cca/testdata/.gitignore -------------------------------------------------------------------------------- /thunder.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "time" 7 | 8 | "github.com/boltdb/bolt" 9 | "github.com/omeid/thunder/codec" 10 | ) 11 | 12 | var ( 13 | ErrKeyValueNotFound = errors.New("Key Value not found.") 14 | ) 15 | 16 | type Options struct { 17 | Timeout time.Duration 18 | NoGrowSync bool 19 | ReadOnly bool 20 | MmapFlags int 21 | KeyCodec codec.Codec 22 | ValueCodec codec.Codec 23 | } 24 | 25 | func Open(path string, mode os.FileMode, options Options) (*DB, error) { 26 | db, err := bolt.Open(path, mode, &bolt.Options{ 27 | Timeout: options.Timeout, 28 | NoGrowSync: options.NoGrowSync, 29 | ReadOnly: options.ReadOnly, 30 | MmapFlags: options.MmapFlags, 31 | }) 32 | return &DB{db, options.KeyCodec, options.ValueCodec}, err 33 | } 34 | -------------------------------------------------------------------------------- /thunder_test.go: -------------------------------------------------------------------------------- 1 | package thunder_test 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "testing" 7 | 8 | "github.com/omeid/thunder" 9 | "github.com/omeid/thunder/codec/json" 10 | "github.com/omeid/thunder/codec/string" 11 | ) 12 | 13 | type BasicStruct struct { 14 | Complex string 15 | Type int 16 | } 17 | 18 | type Basics []BasicStruct 19 | 20 | func (b Basics) Len() int { return len(b) } 21 | func (b Basics) Less(i, j int) bool { return b[i].Type < b[j].Type } 22 | func (b Basics) Swap(i, j int) { b[i], b[j] = b[j], b[i] } 23 | 24 | var test_items = map[string]BasicStruct{ 25 | "test": {"Much complex", 0}, 26 | "test1": {"Much complex", 1}, 27 | "test2": {"Much complex", 2}, 28 | "test3": {"Much complex", 3}, 29 | "test4": {"Much complex", 4}, 30 | "test5": {"Much complex", 5}, 31 | "test6": {"Much complex", 6}, 32 | "test7": {"Much complex", 7}, 33 | "test8": {"Much complex", 8}, 34 | "test9": {"Much complex", 9}, 35 | "test10": {"Much complex", 10}, 36 | "test11": {"Much complex", 11}, 37 | } 38 | 39 | func TestBucketPutGet(t *testing.T) { 40 | 41 | db, err := thunder.Open("testdata/tmp_json.bd", 0600, thunder.Options{ 42 | KeyCodec: strings.Codec(), 43 | ValueCodec: json.Codec(), 44 | }) 45 | 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | key := "test" 51 | value := BasicStruct{ 52 | "Much complex", 53 | 0, 54 | } 55 | 56 | tx := db.Begin(true) 57 | 58 | tx.CreateBucketIfNotExists("test") 59 | if tx.Err() != nil { 60 | t.Fatal(err) 61 | } 62 | 63 | err = tx.Bucket("test").Put(key, value) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | 68 | err = tx.Commit() 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | 73 | var gvalue BasicStruct 74 | 75 | err = db.Begin(false).Bucket("test").Get(key, &gvalue) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | 80 | if !reflect.DeepEqual(value, gvalue) { 81 | t.Fatalf("Misss match %v != %v", value, gvalue) 82 | } 83 | 84 | db.Close() 85 | 86 | } 87 | 88 | func TestBucketAllWhere(t *testing.T) { 89 | 90 | db, err := thunder.Open("testdata/tmp_json.bd", 0600, thunder.Options{ 91 | KeyCodec: strings.Codec(), 92 | ValueCodec: json.Codec(), 93 | }) 94 | 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | tx := db.Begin(true) 100 | 101 | tx.CreateBucketIfNotExists("test_All") 102 | if tx.Err() != nil { 103 | t.Fatal(err) 104 | } 105 | 106 | err = tx.Bucket("test_All").Insert(test_items) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | 111 | err = tx.Commit() 112 | if err != nil { 113 | t.Fatal(err) 114 | } 115 | 116 | //ALL MAP 117 | items := map[string]BasicStruct{} 118 | 119 | tx = db.Begin(false) 120 | 121 | err = tx.Bucket("test_All").All(&items) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | 126 | err = tx.Rollback() 127 | if err != nil { 128 | t.Fatal(err) 129 | } 130 | 131 | if !reflect.DeepEqual(test_items, items) { 132 | t.Fatalf("Misss match %v != %v", test_items, items) 133 | } 134 | 135 | var values_slice Basics 136 | 137 | for _, v := range test_items { 138 | values_slice = append(values_slice, v) 139 | } 140 | 141 | sort.Sort(values_slice) 142 | //ALL Slice 143 | items_slice := Basics{} 144 | 145 | tx = db.Begin(false) 146 | err = tx.Bucket("test_All").All(&items_slice) 147 | if err != nil { 148 | t.Fatal(err) 149 | } 150 | 151 | sort.Sort(items_slice) 152 | if !reflect.DeepEqual(items_slice, values_slice) { 153 | t.Fatalf("Misss match %v != %v", items_slice, values_slice) 154 | } 155 | 156 | test_odd := map[string]BasicStruct{} 157 | 158 | for key, value := range test_items { 159 | if value.Type%2 != 0 { 160 | test_odd[key] = value 161 | } 162 | } 163 | 164 | items = map[string]BasicStruct{} 165 | 166 | err = db.Begin(false).Bucket("test_All").Where(func(v interface{}) bool { 167 | t, ok := v.(BasicStruct) 168 | if !ok { 169 | return false 170 | } 171 | 172 | return t.Type%2 != 0 173 | }, 174 | &items) 175 | if err != nil { 176 | t.Fatal(err) 177 | } 178 | 179 | if !reflect.DeepEqual(test_odd, items) { 180 | t.Fatalf("Misss match %v != %v", test_odd, items) 181 | } 182 | 183 | err = tx.Rollback() 184 | if err != nil { 185 | t.Fatal(err) 186 | } 187 | 188 | db.Close() 189 | 190 | } 191 | -------------------------------------------------------------------------------- /tx.go: -------------------------------------------------------------------------------- 1 | package thunder 2 | 3 | import ( 4 | "github.com/boltdb/bolt" 5 | "github.com/omeid/thunder/codec" 6 | ) 7 | 8 | type Tx struct { 9 | tx *bolt.Tx 10 | 11 | kc codec.Codec 12 | vc codec.Codec 13 | 14 | err error 15 | } 16 | 17 | func (tx *Tx) Err() error { 18 | return tx.err 19 | } 20 | 21 | func (tx *Tx) Commit() error { 22 | return tx.tx.Commit() 23 | } 24 | 25 | func (tx *Tx) Rollback() error { 26 | return tx.tx.Rollback() 27 | } 28 | 29 | func (tx *Tx) CreateBucketIfNotExists(name interface{}) *Bucket { 30 | if tx.err != nil { 31 | return &Bucket{nil, tx.kc, tx.vc, tx.err} 32 | } 33 | 34 | n, err := tx.kc.Marshaler(name).MarshalBinary() 35 | if err != nil { 36 | return &Bucket{nil, tx.kc, tx.vc, err} 37 | } 38 | 39 | bucket, err := tx.tx.CreateBucketIfNotExists(n) 40 | return &Bucket{ 41 | bucket: bucket, 42 | kc: tx.kc, 43 | vc: tx.vc, 44 | err: err, 45 | } 46 | 47 | } 48 | 49 | func (tx *Tx) Bucket(name interface{}) *Bucket { 50 | 51 | if tx.err != nil { 52 | return &Bucket{nil, tx.kc, tx.vc, tx.err} 53 | } 54 | 55 | n, err := tx.kc.Marshaler(name).MarshalBinary() 56 | 57 | bucket := tx.tx.Bucket(n) 58 | if bucket == nil { 59 | err = bolt.ErrBucketNotFound 60 | } 61 | 62 | return &Bucket{ 63 | bucket: bucket, 64 | kc: tx.kc, 65 | vc: tx.vc, 66 | err: err, 67 | } 68 | } 69 | 70 | func (tx *Tx) Cursor() *Cursor { 71 | return &Cursor{ 72 | cursor: tx.tx.Cursor(), 73 | kc: tx.kc, 74 | vc: tx.vc, 75 | err: tx.err, 76 | } 77 | } 78 | --------------------------------------------------------------------------------