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  }