github.com/safing/portbase@v0.19.5/database/interface.go (about) 1 package database 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sync" 8 "time" 9 10 "github.com/bluele/gcache" 11 "github.com/tevino/abool" 12 13 "github.com/safing/portbase/database/accessor" 14 "github.com/safing/portbase/database/iterator" 15 "github.com/safing/portbase/database/query" 16 "github.com/safing/portbase/database/record" 17 ) 18 19 const ( 20 getDBFromKey = "" 21 ) 22 23 // Interface provides a method to access the database with attached options. 24 type Interface struct { 25 options *Options 26 cache gcache.Cache 27 28 writeCache map[string]record.Record 29 writeCacheLock sync.Mutex 30 triggerCacheWrite chan struct{} 31 } 32 33 // Options holds options that may be set for an Interface instance. 34 type Options struct { 35 // Local specifies if the interface is used by an actor on the local device. 36 // Setting both the Local and Internal flags will bring performance 37 // improvements because less checks are needed. 38 Local bool 39 40 // Internal specifies if the interface is used by an actor within the 41 // software. Setting both the Local and Internal flags will bring performance 42 // improvements because less checks are needed. 43 Internal bool 44 45 // AlwaysMakeSecret will have the interface mark all saved records as secret. 46 // This means that they will be only accessible by an internal interface. 47 AlwaysMakeSecret bool 48 49 // AlwaysMakeCrownjewel will have the interface mark all saved records as 50 // crown jewels. This means that they will be only accessible by a local 51 // interface. 52 AlwaysMakeCrownjewel bool 53 54 // AlwaysSetRelativateExpiry will have the interface set a relative expiry, 55 // based on the current time, on all saved records. 56 AlwaysSetRelativateExpiry int64 57 58 // AlwaysSetAbsoluteExpiry will have the interface set an absolute expiry on 59 // all saved records. 60 AlwaysSetAbsoluteExpiry int64 61 62 // CacheSize defines that a cache should be used for this interface and 63 // defines it's size. 64 // Caching comes with an important caveat: If database records are changed 65 // from another interface, the cache will not be invalidated for these 66 // records. It will therefore serve outdated data until that record is 67 // evicted from the cache. 68 CacheSize int 69 70 // DelayCachedWrites defines a database name for which cache writes should 71 // be cached and batched. The database backend must support the Batcher 72 // interface. This option is only valid if used with a cache. 73 // Additionally, this may only be used for internal and local interfaces. 74 // Please note that this means that other interfaces will not be able to 75 // guarantee to serve the latest record if records are written this way. 76 DelayCachedWrites string 77 } 78 79 // Apply applies options to the record metadata. 80 func (o *Options) Apply(r record.Record) { 81 r.UpdateMeta() 82 if o.AlwaysMakeSecret { 83 r.Meta().MakeSecret() 84 } 85 if o.AlwaysMakeCrownjewel { 86 r.Meta().MakeCrownJewel() 87 } 88 if o.AlwaysSetAbsoluteExpiry > 0 { 89 r.Meta().SetAbsoluteExpiry(o.AlwaysSetAbsoluteExpiry) 90 } else if o.AlwaysSetRelativateExpiry > 0 { 91 r.Meta().SetRelativateExpiry(o.AlwaysSetRelativateExpiry) 92 } 93 } 94 95 // HasAllPermissions returns whether the options specify the highest possible 96 // permissions for operations. 97 func (o *Options) HasAllPermissions() bool { 98 return o.Local && o.Internal 99 } 100 101 // hasAccessPermission checks if the interface options permit access to the 102 // given record, locking the record for accessing it's attributes. 103 func (o *Options) hasAccessPermission(r record.Record) bool { 104 // Check if the options specify all permissions, which makes checking the 105 // record unnecessary. 106 if o.HasAllPermissions() { 107 return true 108 } 109 110 r.Lock() 111 defer r.Unlock() 112 113 // Check permissions against record. 114 return r.Meta().CheckPermission(o.Local, o.Internal) 115 } 116 117 // NewInterface returns a new Interface to the database. 118 func NewInterface(opts *Options) *Interface { 119 if opts == nil { 120 opts = &Options{} 121 } 122 123 newIface := &Interface{ 124 options: opts, 125 } 126 if opts.CacheSize > 0 { 127 cacheBuilder := gcache.New(opts.CacheSize).ARC() 128 if opts.DelayCachedWrites != "" { 129 cacheBuilder.EvictedFunc(newIface.cacheEvictHandler) 130 newIface.writeCache = make(map[string]record.Record, opts.CacheSize/2) 131 newIface.triggerCacheWrite = make(chan struct{}) 132 } 133 newIface.cache = cacheBuilder.Build() 134 } 135 return newIface 136 } 137 138 // Exists return whether a record with the given key exists. 139 func (i *Interface) Exists(key string) (bool, error) { 140 _, err := i.Get(key) 141 if err != nil { 142 switch { 143 case errors.Is(err, ErrNotFound): 144 return false, nil 145 case errors.Is(err, ErrPermissionDenied): 146 return true, nil 147 default: 148 return false, err 149 } 150 } 151 return true, nil 152 } 153 154 // Get return the record with the given key. 155 func (i *Interface) Get(key string) (record.Record, error) { 156 r, _, err := i.getRecord(getDBFromKey, key, false) 157 return r, err 158 } 159 160 func (i *Interface) getRecord(dbName string, dbKey string, mustBeWriteable bool) (r record.Record, db *Controller, err error) { //nolint:unparam 161 if dbName == "" { 162 dbName, dbKey = record.ParseKey(dbKey) 163 } 164 165 db, err = getController(dbName) 166 if err != nil { 167 return nil, nil, err 168 } 169 170 if mustBeWriteable && db.ReadOnly() { 171 return nil, db, ErrReadOnly 172 } 173 174 r = i.checkCache(dbName + ":" + dbKey) 175 if r != nil { 176 if !i.options.hasAccessPermission(r) { 177 return nil, db, ErrPermissionDenied 178 } 179 return r, db, nil 180 } 181 182 r, err = db.Get(dbKey) 183 if err != nil { 184 return nil, db, err 185 } 186 187 if !i.options.hasAccessPermission(r) { 188 return nil, db, ErrPermissionDenied 189 } 190 191 r.Lock() 192 ttl := r.Meta().GetRelativeExpiry() 193 r.Unlock() 194 i.updateCache( 195 r, 196 false, // writing 197 false, // remove 198 ttl, // expiry 199 ) 200 201 return r, db, nil 202 } 203 204 func (i *Interface) getMeta(dbName string, dbKey string, mustBeWriteable bool) (m *record.Meta, db *Controller, err error) { //nolint:unparam 205 if dbName == "" { 206 dbName, dbKey = record.ParseKey(dbKey) 207 } 208 209 db, err = getController(dbName) 210 if err != nil { 211 return nil, nil, err 212 } 213 214 if mustBeWriteable && db.ReadOnly() { 215 return nil, db, ErrReadOnly 216 } 217 218 r := i.checkCache(dbName + ":" + dbKey) 219 if r != nil { 220 if !i.options.hasAccessPermission(r) { 221 return nil, db, ErrPermissionDenied 222 } 223 return r.Meta(), db, nil 224 } 225 226 m, err = db.GetMeta(dbKey) 227 if err != nil { 228 return nil, db, err 229 } 230 231 if !m.CheckPermission(i.options.Local, i.options.Internal) { 232 return nil, db, ErrPermissionDenied 233 } 234 235 return m, db, nil 236 } 237 238 // InsertValue inserts a value into a record. 239 func (i *Interface) InsertValue(key string, attribute string, value interface{}) error { 240 r, db, err := i.getRecord(getDBFromKey, key, true) 241 if err != nil { 242 return err 243 } 244 245 r.Lock() 246 defer r.Unlock() 247 248 var acc accessor.Accessor 249 if r.IsWrapped() { 250 wrapper, ok := r.(*record.Wrapper) 251 if !ok { 252 return errors.New("record is malformed (reports to be wrapped but is not of type *record.Wrapper)") 253 } 254 acc = accessor.NewJSONBytesAccessor(&wrapper.Data) 255 } else { 256 acc = accessor.NewStructAccessor(r) 257 } 258 259 err = acc.Set(attribute, value) 260 if err != nil { 261 return fmt.Errorf("failed to set value with %s: %w", acc.Type(), err) 262 } 263 264 i.options.Apply(r) 265 return db.Put(r) 266 } 267 268 // Put saves a record to the database. 269 func (i *Interface) Put(r record.Record) (err error) { 270 // get record or only database 271 var db *Controller 272 if !i.options.HasAllPermissions() { 273 _, db, err = i.getMeta(r.DatabaseName(), r.DatabaseKey(), true) 274 if err != nil && !errors.Is(err, ErrNotFound) { 275 return err 276 } 277 } else { 278 db, err = getController(r.DatabaseName()) 279 if err != nil { 280 return err 281 } 282 } 283 284 // Check if database is read only. 285 if db.ReadOnly() { 286 return ErrReadOnly 287 } 288 289 r.Lock() 290 i.options.Apply(r) 291 remove := r.Meta().IsDeleted() 292 ttl := r.Meta().GetRelativeExpiry() 293 r.Unlock() 294 295 // The record may not be locked when updating the cache. 296 written := i.updateCache(r, true, remove, ttl) 297 if written { 298 return nil 299 } 300 301 r.Lock() 302 defer r.Unlock() 303 return db.Put(r) 304 } 305 306 // PutNew saves a record to the database as a new record (ie. with new timestamps). 307 func (i *Interface) PutNew(r record.Record) (err error) { 308 // get record or only database 309 var db *Controller 310 if !i.options.HasAllPermissions() { 311 _, db, err = i.getMeta(r.DatabaseName(), r.DatabaseKey(), true) 312 if err != nil && !errors.Is(err, ErrNotFound) { 313 return err 314 } 315 } else { 316 db, err = getController(r.DatabaseName()) 317 if err != nil { 318 return err 319 } 320 } 321 322 // Check if database is read only. 323 if db.ReadOnly() { 324 return ErrReadOnly 325 } 326 327 r.Lock() 328 if r.Meta() != nil { 329 r.Meta().Reset() 330 } 331 i.options.Apply(r) 332 remove := r.Meta().IsDeleted() 333 ttl := r.Meta().GetRelativeExpiry() 334 r.Unlock() 335 336 // The record may not be locked when updating the cache. 337 written := i.updateCache(r, true, remove, ttl) 338 if written { 339 return nil 340 } 341 342 r.Lock() 343 defer r.Unlock() 344 return db.Put(r) 345 } 346 347 // PutMany stores many records in the database. 348 // Warning: This is nearly a direct database access and omits many things: 349 // - Record locking 350 // - Hooks 351 // - Subscriptions 352 // - Caching 353 // Use with care. 354 func (i *Interface) PutMany(dbName string) (put func(record.Record) error) { 355 interfaceBatch := make(chan record.Record, 100) 356 357 // permission check 358 if !i.options.HasAllPermissions() { 359 return func(r record.Record) error { 360 return ErrPermissionDenied 361 } 362 } 363 364 // get database 365 db, err := getController(dbName) 366 if err != nil { 367 return func(r record.Record) error { 368 return err 369 } 370 } 371 372 // Check if database is read only. 373 if db.ReadOnly() { 374 return func(r record.Record) error { 375 return ErrReadOnly 376 } 377 } 378 379 // start database access 380 dbBatch, errs := db.PutMany() 381 finished := abool.New() 382 var internalErr error 383 384 // interface options proxy 385 go func() { 386 defer close(dbBatch) // signify that we are finished 387 for { 388 select { 389 case r := <-interfaceBatch: 390 // finished? 391 if r == nil { 392 return 393 } 394 // apply options 395 i.options.Apply(r) 396 // pass along 397 dbBatch <- r 398 case <-time.After(1 * time.Second): 399 // bail out 400 internalErr = errors.New("timeout: putmany unused for too long") 401 finished.Set() 402 return 403 } 404 } 405 }() 406 407 return func(r record.Record) error { 408 // finished? 409 if finished.IsSet() { 410 // check for internal error 411 if internalErr != nil { 412 return internalErr 413 } 414 // check for previous error 415 select { 416 case err := <-errs: 417 return err 418 default: 419 return errors.New("batch is closed") 420 } 421 } 422 423 // finish? 424 if r == nil { 425 finished.Set() 426 interfaceBatch <- nil // signify that we are finished 427 // do not close, as this fn could be called again with nil. 428 return <-errs 429 } 430 431 // check record scope 432 if r.DatabaseName() != dbName { 433 return errors.New("record out of database scope") 434 } 435 436 // submit 437 select { 438 case interfaceBatch <- r: 439 return nil 440 case err := <-errs: 441 return err 442 } 443 } 444 } 445 446 // SetAbsoluteExpiry sets an absolute record expiry. 447 func (i *Interface) SetAbsoluteExpiry(key string, time int64) error { 448 r, db, err := i.getRecord(getDBFromKey, key, true) 449 if err != nil { 450 return err 451 } 452 453 r.Lock() 454 defer r.Unlock() 455 456 i.options.Apply(r) 457 r.Meta().SetAbsoluteExpiry(time) 458 return db.Put(r) 459 } 460 461 // SetRelativateExpiry sets a relative (self-updating) record expiry. 462 func (i *Interface) SetRelativateExpiry(key string, duration int64) error { 463 r, db, err := i.getRecord(getDBFromKey, key, true) 464 if err != nil { 465 return err 466 } 467 468 r.Lock() 469 defer r.Unlock() 470 471 i.options.Apply(r) 472 r.Meta().SetRelativateExpiry(duration) 473 return db.Put(r) 474 } 475 476 // MakeSecret marks the record as a secret, meaning interfacing processes, such as an UI, are denied access to the record. 477 func (i *Interface) MakeSecret(key string) error { 478 r, db, err := i.getRecord(getDBFromKey, key, true) 479 if err != nil { 480 return err 481 } 482 483 r.Lock() 484 defer r.Unlock() 485 486 i.options.Apply(r) 487 r.Meta().MakeSecret() 488 return db.Put(r) 489 } 490 491 // MakeCrownJewel marks a record as a crown jewel, meaning it will only be accessible locally. 492 func (i *Interface) MakeCrownJewel(key string) error { 493 r, db, err := i.getRecord(getDBFromKey, key, true) 494 if err != nil { 495 return err 496 } 497 498 r.Lock() 499 defer r.Unlock() 500 501 i.options.Apply(r) 502 r.Meta().MakeCrownJewel() 503 return db.Put(r) 504 } 505 506 // Delete deletes a record from the database. 507 func (i *Interface) Delete(key string) error { 508 r, db, err := i.getRecord(getDBFromKey, key, true) 509 if err != nil { 510 return err 511 } 512 513 // Check if database is read only. 514 if db.ReadOnly() { 515 return ErrReadOnly 516 } 517 518 i.options.Apply(r) 519 r.Meta().Delete() 520 return db.Put(r) 521 } 522 523 // Query executes the given query on the database. 524 // Will not see data that is in the write cache, waiting to be written. 525 // Use with care with caching. 526 func (i *Interface) Query(q *query.Query) (*iterator.Iterator, error) { 527 _, err := q.Check() 528 if err != nil { 529 return nil, err 530 } 531 532 db, err := getController(q.DatabaseName()) 533 if err != nil { 534 return nil, err 535 } 536 537 // TODO: Finish caching system integration. 538 // Flush the cache before we query the database. 539 // i.FlushCache() 540 541 return db.Query(q, i.options.Local, i.options.Internal) 542 } 543 544 // Purge deletes all records that match the given query. It returns the number 545 // of successful deletes and an error. 546 func (i *Interface) Purge(ctx context.Context, q *query.Query) (int, error) { 547 _, err := q.Check() 548 if err != nil { 549 return 0, err 550 } 551 552 db, err := getController(q.DatabaseName()) 553 if err != nil { 554 return 0, err 555 } 556 557 // Check if database is read only before we add to the cache. 558 if db.ReadOnly() { 559 return 0, ErrReadOnly 560 } 561 562 return db.Purge(ctx, q, i.options.Local, i.options.Internal) 563 } 564 565 // Subscribe subscribes to updates matching the given query. 566 func (i *Interface) Subscribe(q *query.Query) (*Subscription, error) { 567 _, err := q.Check() 568 if err != nil { 569 return nil, err 570 } 571 572 c, err := getController(q.DatabaseName()) 573 if err != nil { 574 return nil, err 575 } 576 577 sub := &Subscription{ 578 q: q, 579 local: i.options.Local, 580 internal: i.options.Internal, 581 Feed: make(chan record.Record, 1000), 582 } 583 c.addSubscription(sub) 584 return sub, nil 585 }