github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/pkg/sorted/kvfile/kvfile.go (about) 1 /* 2 Copyright 2013 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package kvfile provides an implementation of sorted.KeyValue 18 // on top of a single mutable database file on disk using 19 // github.com/cznic/kv. 20 package kvfile 21 22 import ( 23 "bytes" 24 "errors" 25 "fmt" 26 "io" 27 "log" 28 "os" 29 "sync" 30 31 "camlistore.org/pkg/jsonconfig" 32 "camlistore.org/pkg/kvutil" 33 "camlistore.org/pkg/sorted" 34 35 "camlistore.org/third_party/github.com/cznic/kv" 36 ) 37 38 var _ sorted.Wiper = (*kvis)(nil) 39 40 func init() { 41 sorted.RegisterKeyValue("kv", newKeyValueFromJSONConfig) 42 } 43 44 // NewStorage is a convenience that calls newKeyValueFromJSONConfig 45 // with file as the kv storage file. 46 func NewStorage(file string) (sorted.KeyValue, error) { 47 return newKeyValueFromJSONConfig(jsonconfig.Obj{"file": file}) 48 } 49 50 // newKeyValueFromJSONConfig returns a KeyValue implementation on top of a 51 // github.com/cznic/kv file. 52 func newKeyValueFromJSONConfig(cfg jsonconfig.Obj) (sorted.KeyValue, error) { 53 file := cfg.RequiredString("file") 54 if err := cfg.Validate(); err != nil { 55 return nil, err 56 } 57 opts := &kv.Options{} 58 db, err := kvutil.Open(file, opts) 59 if err != nil { 60 return nil, err 61 } 62 is := &kvis{ 63 db: db, 64 opts: opts, 65 path: file, 66 } 67 return is, nil 68 } 69 70 type kvis struct { 71 path string 72 db *kv.DB 73 opts *kv.Options 74 txmu sync.Mutex 75 } 76 77 // TODO: use bytepool package. 78 func getBuf(n int) []byte { return make([]byte, n) } 79 func putBuf([]byte) {} 80 81 func (is *kvis) Get(key string) (string, error) { 82 buf := getBuf(200) 83 defer putBuf(buf) 84 val, err := is.db.Get(buf, []byte(key)) 85 if err != nil { 86 return "", err 87 } 88 if val == nil { 89 return "", sorted.ErrNotFound 90 } 91 return string(val), nil 92 } 93 94 func (is *kvis) Set(key, value string) error { 95 return is.db.Set([]byte(key), []byte(value)) 96 } 97 98 func (is *kvis) Delete(key string) error { 99 return is.db.Delete([]byte(key)) 100 } 101 102 func (is *kvis) Find(start, end string) sorted.Iterator { 103 it := &iter{ 104 db: is.db, 105 startKey: start, 106 endKey: []byte(end), 107 } 108 it.enum, _, it.err = it.db.Seek([]byte(start)) 109 return it 110 } 111 112 func (is *kvis) BeginBatch() sorted.BatchMutation { 113 return sorted.NewBatchMutation() 114 } 115 116 func (is *kvis) Wipe() error { 117 // Unlock the already open DB. 118 if err := is.db.Close(); err != nil { 119 return err 120 } 121 if err := os.Remove(is.path); err != nil { 122 return err 123 } 124 125 db, err := kv.Create(is.path, is.opts) 126 if err != nil { 127 return fmt.Errorf("error creating %s: %v", is.path, err) 128 } 129 is.db = db 130 return nil 131 } 132 133 type batch interface { 134 Mutations() []sorted.Mutation 135 } 136 137 func (is *kvis) CommitBatch(bm sorted.BatchMutation) error { 138 b, ok := bm.(batch) 139 if !ok { 140 return errors.New("invalid batch type") 141 } 142 is.txmu.Lock() 143 defer is.txmu.Unlock() 144 145 good := false 146 defer func() { 147 if !good { 148 is.db.Rollback() 149 } 150 }() 151 152 if err := is.db.BeginTransaction(); err != nil { 153 return err 154 } 155 for _, m := range b.Mutations() { 156 if m.IsDelete() { 157 if err := is.db.Delete([]byte(m.Key())); err != nil { 158 return err 159 } 160 } else { 161 if err := is.db.Set([]byte(m.Key()), []byte(m.Value())); err != nil { 162 return err 163 } 164 } 165 } 166 167 good = true 168 return is.db.Commit() 169 } 170 171 func (is *kvis) Close() error { 172 log.Printf("Closing kvfile database %s", is.path) 173 return is.db.Close() 174 } 175 176 type iter struct { 177 db *kv.DB 178 startKey string 179 endKey []byte 180 181 enum *kv.Enumerator 182 183 valid bool 184 key, val []byte 185 skey, sval *string // non-nil if valid 186 187 err error 188 closed bool 189 } 190 191 func (it *iter) Close() error { 192 it.closed = true 193 return it.err 194 } 195 196 func (it *iter) KeyBytes() []byte { 197 if !it.valid { 198 panic("not valid") 199 } 200 return it.key 201 } 202 203 func (it *iter) Key() string { 204 if !it.valid { 205 panic("not valid") 206 } 207 if it.skey != nil { 208 return *it.skey 209 } 210 str := string(it.key) 211 it.skey = &str 212 return str 213 } 214 215 func (it *iter) ValueBytes() []byte { 216 if !it.valid { 217 panic("not valid") 218 } 219 return it.val 220 } 221 222 func (it *iter) Value() string { 223 if !it.valid { 224 panic("not valid") 225 } 226 if it.sval != nil { 227 return *it.sval 228 } 229 str := string(it.val) 230 it.sval = &str 231 return str 232 } 233 234 func (it *iter) end() bool { 235 it.valid = false 236 it.closed = true 237 return false 238 } 239 240 func (it *iter) Next() (ret bool) { 241 if it.err != nil { 242 return false 243 } 244 if it.closed { 245 panic("Next called after Next returned value") 246 } 247 it.skey, it.sval = nil, nil 248 var err error 249 it.key, it.val, err = it.enum.Next() 250 if err == io.EOF { 251 it.err = nil 252 return it.end() 253 } 254 if err != nil { 255 it.err = err 256 return it.end() 257 } 258 if len(it.endKey) > 0 && bytes.Compare(it.key, it.endKey) >= 0 { 259 return it.end() 260 } 261 it.valid = true 262 return true 263 }