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 }