github.com/safing/portbase@v0.19.5/database/storage/badger/badger.go (about)

     1  package badger
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/dgraph-io/badger"
    10  
    11  	"github.com/safing/portbase/database/iterator"
    12  	"github.com/safing/portbase/database/query"
    13  	"github.com/safing/portbase/database/record"
    14  	"github.com/safing/portbase/database/storage"
    15  	"github.com/safing/portbase/log"
    16  )
    17  
    18  // Badger database made pluggable for portbase.
    19  type Badger struct {
    20  	name string
    21  	db   *badger.DB
    22  }
    23  
    24  func init() {
    25  	_ = storage.Register("badger", NewBadger)
    26  }
    27  
    28  // NewBadger opens/creates a badger database.
    29  func NewBadger(name, location string) (storage.Interface, error) {
    30  	opts := badger.DefaultOptions(location)
    31  
    32  	db, err := badger.Open(opts)
    33  	if errors.Is(err, badger.ErrTruncateNeeded) {
    34  		// clean up after crash
    35  		log.Warningf("database/storage: truncating corrupted value log of badger database %s: this may cause data loss", name)
    36  		opts.Truncate = true
    37  		db, err = badger.Open(opts)
    38  	}
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	return &Badger{
    44  		name: name,
    45  		db:   db,
    46  	}, nil
    47  }
    48  
    49  // Get returns a database record.
    50  func (b *Badger) Get(key string) (record.Record, error) {
    51  	var item *badger.Item
    52  
    53  	err := b.db.View(func(txn *badger.Txn) error {
    54  		var err error
    55  		item, err = txn.Get([]byte(key))
    56  		if err != nil {
    57  			if errors.Is(err, badger.ErrKeyNotFound) {
    58  				return storage.ErrNotFound
    59  			}
    60  			return err
    61  		}
    62  		return nil
    63  	})
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	// return err if deleted or expired
    69  	if item.IsDeletedOrExpired() {
    70  		return nil, storage.ErrNotFound
    71  	}
    72  
    73  	data, err := item.ValueCopy(nil)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	m, err := record.NewRawWrapper(b.name, string(item.Key()), data)
    79  	if err != nil {
    80  		return nil, err
    81  	}
    82  	return m, nil
    83  }
    84  
    85  // GetMeta returns the metadata of a database record.
    86  func (b *Badger) GetMeta(key string) (*record.Meta, error) {
    87  	// TODO: Replace with more performant variant.
    88  
    89  	r, err := b.Get(key)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	return r.Meta(), nil
    95  }
    96  
    97  // Put stores a record in the database.
    98  func (b *Badger) Put(r record.Record) (record.Record, error) {
    99  	data, err := r.MarshalRecord(r)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	err = b.db.Update(func(txn *badger.Txn) error {
   105  		return txn.Set([]byte(r.DatabaseKey()), data)
   106  	})
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	return r, nil
   111  }
   112  
   113  // Delete deletes a record from the database.
   114  func (b *Badger) Delete(key string) error {
   115  	return b.db.Update(func(txn *badger.Txn) error {
   116  		err := txn.Delete([]byte(key))
   117  		if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
   118  			return err
   119  		}
   120  		return nil
   121  	})
   122  }
   123  
   124  // Query returns a an iterator for the supplied query.
   125  func (b *Badger) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) {
   126  	_, err := q.Check()
   127  	if err != nil {
   128  		return nil, fmt.Errorf("invalid query: %w", err)
   129  	}
   130  
   131  	queryIter := iterator.New()
   132  
   133  	go b.queryExecutor(queryIter, q, local, internal)
   134  	return queryIter, nil
   135  }
   136  
   137  //nolint:gocognit
   138  func (b *Badger) queryExecutor(queryIter *iterator.Iterator, q *query.Query, local, internal bool) {
   139  	err := b.db.View(func(txn *badger.Txn) error {
   140  		it := txn.NewIterator(badger.DefaultIteratorOptions)
   141  		defer it.Close()
   142  		prefix := []byte(q.DatabaseKeyPrefix())
   143  		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
   144  			item := it.Item()
   145  
   146  			var data []byte
   147  			err := item.Value(func(val []byte) error {
   148  				data = val
   149  				return nil
   150  			})
   151  			if err != nil {
   152  				return err
   153  			}
   154  
   155  			r, err := record.NewRawWrapper(b.name, string(item.Key()), data)
   156  			if err != nil {
   157  				return err
   158  			}
   159  
   160  			if !r.Meta().CheckValidity() {
   161  				continue
   162  			}
   163  			if !r.Meta().CheckPermission(local, internal) {
   164  				continue
   165  			}
   166  
   167  			if q.MatchesRecord(r) {
   168  				copiedData, err := item.ValueCopy(nil)
   169  				if err != nil {
   170  					return err
   171  				}
   172  				newWrapper, err := record.NewRawWrapper(b.name, r.DatabaseKey(), copiedData)
   173  				if err != nil {
   174  					return err
   175  				}
   176  				select {
   177  				case <-queryIter.Done:
   178  					return nil
   179  				case queryIter.Next <- newWrapper:
   180  				default:
   181  					select {
   182  					case queryIter.Next <- newWrapper:
   183  					case <-queryIter.Done:
   184  						return nil
   185  					case <-time.After(1 * time.Minute):
   186  						return errors.New("query timeout")
   187  					}
   188  				}
   189  			}
   190  
   191  		}
   192  		return nil
   193  	})
   194  
   195  	queryIter.Finish(err)
   196  }
   197  
   198  // ReadOnly returns whether the database is read only.
   199  func (b *Badger) ReadOnly() bool {
   200  	return false
   201  }
   202  
   203  // Injected returns whether the database is injected.
   204  func (b *Badger) Injected() bool {
   205  	return false
   206  }
   207  
   208  // Maintain runs a light maintenance operation on the database.
   209  func (b *Badger) Maintain(_ context.Context) error {
   210  	_ = b.db.RunValueLogGC(0.7)
   211  	return nil
   212  }
   213  
   214  // MaintainThorough runs a thorough maintenance operation on the database.
   215  func (b *Badger) MaintainThorough(_ context.Context) (err error) {
   216  	for err == nil {
   217  		err = b.db.RunValueLogGC(0.7)
   218  	}
   219  	return nil
   220  }
   221  
   222  // MaintainRecordStates maintains records states in the database.
   223  func (b *Badger) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time, shadowDelete bool) error {
   224  	// TODO: implement MaintainRecordStates
   225  	return nil
   226  }
   227  
   228  // Shutdown shuts down the database.
   229  func (b *Badger) Shutdown() error {
   230  	return b.db.Close()
   231  }