github.com/influx6/npkg@v0.8.8/nstorage/nbadger/nbadger.go (about) 1 package nbadger 2 3 import ( 4 regexp2 "regexp" 5 "time" 6 7 "github.com/dgraph-io/badger/v2" 8 9 "github.com/influx6/npkg/nerror" 10 "github.com/influx6/npkg/nstorage" 11 "github.com/influx6/npkg/nunsafe" 12 ) 13 14 var _ nstorage.ExpirableStore = (*BadgerStore)(nil) 15 16 // BadgerStore implements session management, storage and access using Badger as 17 // underline store. 18 type BadgerStore struct { 19 ops badger.Options 20 iter badger.IteratorOptions 21 Db *badger.DB 22 } 23 24 // NewBadgerStore returns a new instance of a Badger store using provided prefix if present. 25 func NewBadgerStore(ops badger.Options, iterator badger.IteratorOptions) (*BadgerStore, error) { 26 var red BadgerStore 27 red.ops = ops 28 red.iter = iterator 29 if err := red.createConnection(); err != nil { 30 return nil, err 31 } 32 return &red, nil 33 } 34 35 // createConnection attempts to create a new Badger connection. 36 func (rd *BadgerStore) createConnection() error { 37 db, err := badger.Open(rd.ops) 38 if err != nil { 39 return nerror.WrapOnly(err) 40 } 41 rd.Db = db 42 return nil 43 } 44 45 // Keys returns all giving keys of elements within store. 46 func (rd *BadgerStore) Keys() ([]string, error) { 47 var keys = make([]string, 0) 48 var err = rd.Db.View(func(txn *badger.Txn) error { 49 var iteratorOption = rd.iter 50 iteratorOption.PrefetchValues = false 51 var iterator = txn.NewIterator(iteratorOption) 52 defer iterator.Close() 53 54 for iterator.Rewind(); iterator.Valid(); iterator.Next() { 55 var item = iterator.Item() 56 if item.IsDeletedOrExpired() { 57 continue 58 } 59 keys = append(keys, nunsafe.Bytes2String(copyBytes(item.Key()))) 60 } 61 return nil 62 }) 63 return keys, err 64 } 65 66 // ScanMatch uses the value of lastKey instead the index to allow scanning keys over 67 // a giving range, this is important and should be maintained and provided for 68 // this to work with BadgerDB. 69 func (rd *BadgerStore) ScanMatch(count int64, _ int64, lastKey string, regexp string) (nstorage.ScanResult, error) { 70 if len(regexp) == 0 { 71 regexp = ".+" 72 } 73 74 var regx, rgErr = regexp2.Compile(regexp) 75 if rgErr != nil { 76 return nstorage.ScanResult{}, nerror.WrapOnly(rgErr) 77 } 78 79 var isFinished bool 80 var keys = make([]string, 0, 10) 81 var err = rd.Db.View(func(txn *badger.Txn) error { 82 var iterator = txn.NewIterator(rd.iter) 83 defer iterator.Close() 84 85 if len(lastKey) != 0 { 86 iterator.Seek(nunsafe.String2Bytes(lastKey)) 87 } else { 88 iterator.Rewind() 89 } 90 91 for ; iterator.Valid(); iterator.Next() { 92 var item = iterator.Item() 93 if item.IsDeletedOrExpired() { 94 continue 95 } 96 if regx != nil && !regx.Match(item.Key()) { 97 continue 98 } 99 100 keys = append(keys, nunsafe.Bytes2String(item.Key())) 101 if currCount := len(keys); currCount >= int(count) { 102 break 103 } 104 } 105 106 isFinished = iterator.Valid() 107 return nil 108 }) 109 if err != nil { 110 return nstorage.ScanResult{}, nerror.WrapOnly(err) 111 } 112 113 var nextKey string 114 if len(keys) != 0 { 115 nextKey = keys[len(keys)-1] 116 } 117 118 return nstorage.ScanResult{ 119 Finished: isFinished, 120 Keys: keys, 121 LastKey: nextKey, 122 }, nil 123 } 124 125 // EachKeyMatch returns all matching results within using giving functions. 126 func (rd *BadgerStore) EachKeyMatch(regexp string) (keys []string, err error) { 127 var generatedRegEx *regexp2.Regexp 128 var rgErr error 129 130 if len(regexp) != 0 { 131 generatedRegEx, rgErr = regexp2.Compile(regexp) 132 if rgErr != nil { 133 return nil, nerror.WrapOnly(rgErr) 134 } 135 } 136 137 keys = make([]string, 0, 10) 138 err = rd.Db.View(func(txn *badger.Txn) error { 139 var iterator = txn.NewIterator(rd.iter) 140 defer iterator.Close() 141 142 for iterator.Rewind(); iterator.Valid(); iterator.Next() { 143 var item = iterator.Item() 144 if item.IsDeletedOrExpired() { 145 continue 146 } 147 if generatedRegEx != nil && !generatedRegEx.Match(item.Key()) { 148 continue 149 } 150 keys = append(keys, nunsafe.Bytes2String(item.Key())) 151 } 152 return nil 153 }) 154 return 155 } 156 157 func (rd *BadgerStore) Count() (int64, error) { 158 var count int64 159 var readErr = rd.Db.View(func(txn *badger.Txn) error { 160 return nil 161 }) 162 if readErr != nil { 163 return -1, nerror.WrapOnly(readErr) 164 } 165 return count, nil 166 } 167 168 // Each runs through all elements for giving store, skipping keys 169 // in Badger who have no data or an empty byte slice. 170 // 171 // Each byte slice provided is only valid for the call of 172 // the function, after which it becomes invalid as it can 173 // be re-used for efficient memory management, so ensure to copy 174 // given byte slice yourself within function to protect against 175 // undefined behaviour. 176 func (rd *BadgerStore) Each(fn nstorage.EachItem) error { 177 return rd.Db.View(func(txn *badger.Txn) error { 178 var iterator = txn.NewIterator(rd.iter) 179 defer iterator.Close() 180 181 for iterator.Rewind(); iterator.Valid(); iterator.Next() { 182 var item = iterator.Item() 183 if item.IsDeletedOrExpired() { 184 continue 185 } 186 var stop = false 187 var err = item.Value(func(value []byte) error { 188 if dataErr := fn(value, string(item.Key())); dataErr != nil { 189 stop = true 190 if nerror.IsAny(dataErr, nstorage.ErrJustStop) { 191 return nil 192 } 193 return dataErr 194 } 195 return nil 196 }) 197 198 if err != nil { 199 return nerror.WrapOnly(err) 200 } 201 202 if stop { 203 return nil 204 } 205 } 206 return nil 207 }) 208 } 209 210 // Exists returns true/false if giving key exists. 211 func (rd *BadgerStore) Exists(key string) (bool, error) { 212 var exist bool 213 if err := rd.Db.View(func(txn *badger.Txn) error { 214 item, err := txn.Get(nunsafe.String2Bytes(key)) 215 if err != nil { 216 return nerror.WrapOnly(err) 217 } 218 if item.IsDeletedOrExpired() { 219 return nil 220 } 221 exist = true 222 return nil 223 }); err != nil { 224 return false, err 225 } 226 return exist, nil 227 } 228 229 // GetAnyKeys returns a list of values for any of the key's found. 230 // Unless a specific error occurred retrieving the value of a key, if a 231 // key is not found then it is ignored and a nil is set in it's place. 232 func (rd *BadgerStore) GetAnyKeys(keys ...string) ([][]byte, error) { 233 var values = make([][]byte, 0, len(keys)) 234 if err := rd.Db.View(func(txn *badger.Txn) error { 235 for _, key := range keys { 236 item, err := txn.Get(nunsafe.String2Bytes(key)) 237 if err != nil { 238 return nerror.WrapOnly(err) 239 } 240 if item.IsDeletedOrExpired() { 241 values = append(values, nil) 242 continue 243 } 244 var value, verr = item.ValueCopy(nil) 245 if verr != nil { 246 return nerror.WrapOnly(verr) 247 } 248 values = append(values, value) 249 } 250 251 return nil 252 }); err != nil { 253 return values, err 254 } 255 return values, nil 256 } 257 258 // GetAllKeys returns a list of values for any of the key's found. 259 // if the value of a key is not found then we stop immediately, returning 260 // an error and the current set of items retreived. 261 func (rd *BadgerStore) GetAllKeys(keys ...string) ([][]byte, error) { 262 var values = make([][]byte, 0, len(keys)) 263 if err := rd.Db.View(func(txn *badger.Txn) error { 264 for _, key := range keys { 265 item, err := txn.Get(nunsafe.String2Bytes(key)) 266 if err != nil { 267 return nerror.WrapOnly(err) 268 } 269 if item.IsDeletedOrExpired() { 270 return nerror.New("not found") 271 } 272 var value, verr = item.ValueCopy(nil) 273 if verr != nil { 274 return nerror.WrapOnly(verr) 275 } 276 values = append(values, value) 277 } 278 279 return nil 280 }); err != nil { 281 return values, err 282 } 283 return values, nil 284 } 285 286 // Get returns giving session stored with giving key, returning an 287 // error if not found. 288 func (rd *BadgerStore) Get(key string) ([]byte, error) { 289 var value []byte 290 if err := rd.Db.View(func(txn *badger.Txn) error { 291 item, err := txn.Get(nunsafe.String2Bytes(key)) 292 if err != nil { 293 return nerror.WrapOnly(err) 294 } 295 if item.IsDeletedOrExpired() { 296 return nerror.New("not found") 297 } 298 299 value, err = item.ValueCopy(nil) 300 if err != nil { 301 return nerror.WrapOnly(err) 302 } 303 304 return nil 305 }); err != nil { 306 return nil, err 307 } 308 return value, nil 309 } 310 311 // Save adds giving session into storage using Badger as underline store. 312 func (rd *BadgerStore) Save(key string, data []byte) error { 313 return rd.SaveTTL(key, data, 0) 314 } 315 316 // SaveTTL adds giving session into storage using Badger as underline store, with provided 317 // expiration. 318 // Duration of 0 means no expiration. 319 func (rd *BadgerStore) SaveTTL(key string, data []byte, expiration time.Duration) error { 320 return rd.Db.Update(func(txn *badger.Txn) error { 321 var op badger.Entry 322 op.Value = data 323 op.Key = nunsafe.String2Bytes(key) 324 325 if expiration > 0 { 326 op.WithTTL(expiration) 327 } 328 329 if err := txn.SetEntry(&op); err != nil { 330 return nerror.WrapOnly(err) 331 } 332 return nil 333 }) 334 } 335 336 // TTL returns giving expiration time for giving key. 337 func (rd *BadgerStore) TTL(key string) (time.Duration, error) { 338 var ttl time.Duration 339 var err = rd.Db.Update(func(txn *badger.Txn) error { 340 var item, err = txn.Get(nunsafe.String2Bytes(key)) 341 if err != nil { 342 return nerror.Wrap(err, "Failed to retrieve key") 343 } 344 if item.IsDeletedOrExpired() { 345 return nerror.New("not found, possibly expired") 346 } 347 348 ttl = ttlDur(item.ExpiresAt(), 0) 349 if ttl < 0 { 350 ttl *= -1 351 } 352 return nil 353 }) 354 return ttl, err 355 } 356 357 // Close updates to disk. 358 func (rd *BadgerStore) Close() error { 359 return rd.Db.Close() 360 } 361 362 // Sync updates to disk. 363 func (rd *BadgerStore) Sync() error { 364 return rd.Db.Sync() 365 } 366 367 // ExtendTTL resets new TTL for giving key if it has not expired and is still accessible. 368 // 369 // A expiration value of zero means to persist the giving key. 370 func (rd *BadgerStore) ExtendTTL(key string, expiration time.Duration) error { 371 return rd.Db.Update(func(txn *badger.Txn) error { 372 var item, err = txn.Get(nunsafe.String2Bytes(key)) 373 if err != nil { 374 return nerror.Wrap(err, "Failed to retrieve key") 375 } 376 if item.IsDeletedOrExpired() { 377 return nerror.New("not found, possibly expired") 378 } 379 380 var lastTTL = ttlDur(item.ExpiresAt(), 0) 381 if lastTTL < 0 { 382 lastTTL *= -1 383 } 384 385 value, err := item.ValueCopy(nil) 386 if err != nil { 387 return nerror.Wrap(err, "failed to delete") 388 } 389 390 // delete old key 391 if err := txn.Delete(nunsafe.String2Bytes(key)); err != nil { 392 return nerror.Wrap(err, "failed to delete") 393 } 394 395 var op badger.Entry 396 op.Value = value 397 op.Key = nunsafe.String2Bytes(key) 398 399 if expiration > 0 { 400 op.WithTTL(lastTTL + expiration) 401 } 402 403 if err := txn.SetEntry(&op); err != nil { 404 return nerror.Wrap(err, "failed to save") 405 } 406 return nil 407 }) 408 } 409 410 // ResetTTL resets expiration or sets expiration of giving key if it has not 411 // expired yet. 412 // 413 // A expiration value of zero means to persist the giving key. 414 func (rd *BadgerStore) ResetTTL(key string, expiration time.Duration) error { 415 return rd.Db.Update(func(txn *badger.Txn) error { 416 var item, err = txn.Get(nunsafe.String2Bytes(key)) 417 if err != nil { 418 return nerror.Wrap(err, "Failed to retrieve key") 419 } 420 if item.IsDeletedOrExpired() { 421 return nerror.New("not found, possibly expired") 422 } 423 424 value, err := item.ValueCopy(nil) 425 if err != nil { 426 return err 427 } 428 429 var op badger.Entry 430 op.Value = value 431 op.Key = nunsafe.String2Bytes(key) 432 433 if expiration > 0 { 434 op.WithTTL(expiration) 435 } 436 437 if err := txn.SetEntry(&op); err != nil { 438 return err 439 } 440 return nil 441 }) 442 } 443 444 // Update updates giving session stored with giving key. It updates 445 // the underline data for key, setting a 0 ttl duration. 446 func (rd *BadgerStore) Update(key string, data []byte) error { 447 return rd.UpdateTTL(key, data, 0) 448 } 449 450 // UpdateTTL updates giving session stored with giving key. It updates 451 // the underline data. 452 // If a key has expired, and was deleted, then a error is returned. 453 // 454 // if expiration is zero then giving value expiration will not be reset but left 455 // as is. 456 func (rd *BadgerStore) UpdateTTL(key string, data []byte, expiration time.Duration) error { 457 return rd.Db.Update(func(txn *badger.Txn) error { 458 var item, err = txn.Get(nunsafe.String2Bytes(key)) 459 if err != nil { 460 return nerror.Wrap(err, "Failed to retrieve key") 461 } 462 if item.IsDeletedOrExpired() { 463 return nerror.New("not found, possibly expired") 464 } 465 466 var op badger.Entry 467 op.Value = data 468 op.Key = nunsafe.String2Bytes(key) 469 470 if expiration > 0 { 471 op.WithTTL(expiration) 472 } 473 474 if err := txn.SetEntry(&op); err != nil { 475 return err 476 } 477 478 return nil 479 }) 480 } 481 482 // Remove removes underline key from the Badger store after retrieving it and 483 // returning giving session. 484 func (rd *BadgerStore) Remove(key string) ([]byte, error) { 485 var old []byte 486 err := rd.Db.Update(func(txn *badger.Txn) error { 487 var item, err = txn.Get(nunsafe.String2Bytes(key)) 488 if err != nil { 489 return err 490 } 491 492 old = make([]byte, item.ValueSize()) 493 old, err = item.ValueCopy(old) 494 if err != nil { 495 return err 496 } 497 498 return txn.Delete(nunsafe.String2Bytes(key)) 499 }) 500 return old, err 501 } 502 503 // RemoveKeys removes all keys found in the store. 504 func (rd *BadgerStore) RemoveKeys(keys ...string) error { 505 if err := rd.Db.Update(func(txn *badger.Txn) error { 506 for _, key := range keys { 507 err := txn.Delete(nunsafe.String2Bytes(key)) 508 if err != nil && err != badger.ErrEmptyKey && err != badger.ErrKeyNotFound { 509 return nerror.WrapOnly(err) 510 } 511 return err 512 } 513 514 return nil 515 }); err != nil { 516 return nerror.WrapOnly(err) 517 } 518 return nil 519 } 520 521 // ***************************************************** 522 // internal methods 523 // ***************************************************** 524 525 func copyBytes(bu []byte) []byte { 526 var cu = make([]byte, len(bu)) 527 copy(cu, bu) 528 return cu 529 } 530 531 func ttlDur(tx uint64, ns int64) time.Duration { 532 if tx == 0 { 533 return 0 534 } 535 var ttl = time.Unix(int64(tx), ns) 536 return time.Now().Sub(ttl) 537 }