github.com/safing/portbase@v0.19.5/database/controller.go (about) 1 package database 2 3 import ( 4 "context" 5 "errors" 6 "sync" 7 "time" 8 9 "github.com/safing/portbase/database/iterator" 10 "github.com/safing/portbase/database/query" 11 "github.com/safing/portbase/database/record" 12 "github.com/safing/portbase/database/storage" 13 ) 14 15 // A Controller takes care of all the extra database logic. 16 type Controller struct { 17 database *Database 18 storage storage.Interface 19 shadowDelete bool 20 21 hooksLock sync.RWMutex 22 hooks []*RegisteredHook 23 24 subscriptionLock sync.RWMutex 25 subscriptions []*Subscription 26 } 27 28 // newController creates a new controller for a storage. 29 func newController(database *Database, storageInt storage.Interface, shadowDelete bool) *Controller { 30 return &Controller{ 31 database: database, 32 storage: storageInt, 33 shadowDelete: shadowDelete, 34 } 35 } 36 37 // ReadOnly returns whether the storage is read only. 38 func (c *Controller) ReadOnly() bool { 39 return c.storage.ReadOnly() 40 } 41 42 // Injected returns whether the storage is injected. 43 func (c *Controller) Injected() bool { 44 return c.storage.Injected() 45 } 46 47 // Get returns the record with the given key. 48 func (c *Controller) Get(key string) (record.Record, error) { 49 if shuttingDown.IsSet() { 50 return nil, ErrShuttingDown 51 } 52 53 if err := c.runPreGetHooks(key); err != nil { 54 return nil, err 55 } 56 57 r, err := c.storage.Get(key) 58 if err != nil { 59 // replace not found error 60 if errors.Is(err, storage.ErrNotFound) { 61 return nil, ErrNotFound 62 } 63 return nil, err 64 } 65 66 r.Lock() 67 defer r.Unlock() 68 69 r, err = c.runPostGetHooks(r) 70 if err != nil { 71 return nil, err 72 } 73 74 if !r.Meta().CheckValidity() { 75 return nil, ErrNotFound 76 } 77 78 return r, nil 79 } 80 81 // GetMeta returns the metadata of the record with the given key. 82 func (c *Controller) GetMeta(key string) (*record.Meta, error) { 83 if shuttingDown.IsSet() { 84 return nil, ErrShuttingDown 85 } 86 87 var m *record.Meta 88 var err error 89 if metaDB, ok := c.storage.(storage.MetaHandler); ok { 90 m, err = metaDB.GetMeta(key) 91 if err != nil { 92 // replace not found error 93 if errors.Is(err, storage.ErrNotFound) { 94 return nil, ErrNotFound 95 } 96 return nil, err 97 } 98 } else { 99 r, err := c.storage.Get(key) 100 if err != nil { 101 // replace not found error 102 if errors.Is(err, storage.ErrNotFound) { 103 return nil, ErrNotFound 104 } 105 return nil, err 106 } 107 m = r.Meta() 108 } 109 110 if !m.CheckValidity() { 111 return nil, ErrNotFound 112 } 113 114 return m, nil 115 } 116 117 // Put saves a record in the database, executes any registered 118 // pre-put hooks and finally send an update to all subscribers. 119 // The record must be locked and secured from concurrent access 120 // when calling Put(). 121 func (c *Controller) Put(r record.Record) (err error) { 122 if shuttingDown.IsSet() { 123 return ErrShuttingDown 124 } 125 126 if c.ReadOnly() { 127 return ErrReadOnly 128 } 129 130 r, err = c.runPrePutHooks(r) 131 if err != nil { 132 return err 133 } 134 135 if !c.shadowDelete && r.Meta().IsDeleted() { 136 // Immediate delete. 137 err = c.storage.Delete(r.DatabaseKey()) 138 } else { 139 // Put or shadow delete. 140 r, err = c.storage.Put(r) 141 } 142 143 if err != nil { 144 return err 145 } 146 147 if r == nil { 148 return errors.New("storage returned nil record after successful put operation") 149 } 150 151 c.notifySubscribers(r) 152 153 return nil 154 } 155 156 // PutMany stores many records in the database. It does not 157 // process any hooks or update subscriptions. Use with care! 158 func (c *Controller) PutMany() (chan<- record.Record, <-chan error) { 159 if shuttingDown.IsSet() { 160 errs := make(chan error, 1) 161 errs <- ErrShuttingDown 162 return make(chan record.Record), errs 163 } 164 165 if c.ReadOnly() { 166 errs := make(chan error, 1) 167 errs <- ErrReadOnly 168 return make(chan record.Record), errs 169 } 170 171 if batcher, ok := c.storage.(storage.Batcher); ok { 172 return batcher.PutMany(c.shadowDelete) 173 } 174 175 errs := make(chan error, 1) 176 errs <- ErrNotImplemented 177 return make(chan record.Record), errs 178 } 179 180 // Query executes the given query on the database. 181 func (c *Controller) Query(q *query.Query, local, internal bool) (*iterator.Iterator, error) { 182 if shuttingDown.IsSet() { 183 return nil, ErrShuttingDown 184 } 185 186 it, err := c.storage.Query(q, local, internal) 187 if err != nil { 188 return nil, err 189 } 190 191 return it, nil 192 } 193 194 // PushUpdate pushes a record update to subscribers. 195 // The caller must hold the record's lock when calling 196 // PushUpdate. 197 func (c *Controller) PushUpdate(r record.Record) { 198 if c != nil { 199 if shuttingDown.IsSet() { 200 return 201 } 202 203 c.notifySubscribers(r) 204 } 205 } 206 207 func (c *Controller) addSubscription(sub *Subscription) { 208 if shuttingDown.IsSet() { 209 return 210 } 211 212 c.subscriptionLock.Lock() 213 defer c.subscriptionLock.Unlock() 214 215 c.subscriptions = append(c.subscriptions, sub) 216 } 217 218 // Maintain runs the Maintain method on the storage. 219 func (c *Controller) Maintain(ctx context.Context) error { 220 if shuttingDown.IsSet() { 221 return ErrShuttingDown 222 } 223 224 if maintainer, ok := c.storage.(storage.Maintainer); ok { 225 return maintainer.Maintain(ctx) 226 } 227 return nil 228 } 229 230 // MaintainThorough runs the MaintainThorough method on the 231 // storage. 232 func (c *Controller) MaintainThorough(ctx context.Context) error { 233 if shuttingDown.IsSet() { 234 return ErrShuttingDown 235 } 236 237 if maintainer, ok := c.storage.(storage.Maintainer); ok { 238 return maintainer.MaintainThorough(ctx) 239 } 240 return nil 241 } 242 243 // MaintainRecordStates runs the record state lifecycle 244 // maintenance on the storage. 245 func (c *Controller) MaintainRecordStates(ctx context.Context, purgeDeletedBefore time.Time) error { 246 if shuttingDown.IsSet() { 247 return ErrShuttingDown 248 } 249 250 return c.storage.MaintainRecordStates(ctx, purgeDeletedBefore, c.shadowDelete) 251 } 252 253 // Purge deletes all records that match the given query. 254 // It returns the number of successful deletes and an error. 255 func (c *Controller) Purge(ctx context.Context, q *query.Query, local, internal bool) (int, error) { 256 if shuttingDown.IsSet() { 257 return 0, ErrShuttingDown 258 } 259 260 if purger, ok := c.storage.(storage.Purger); ok { 261 return purger.Purge(ctx, q, local, internal, c.shadowDelete) 262 } 263 264 return 0, ErrNotImplemented 265 } 266 267 // Shutdown shuts down the storage. 268 func (c *Controller) Shutdown() error { 269 return c.storage.Shutdown() 270 } 271 272 // notifySubscribers notifies all subscribers that are interested 273 // in r. r must be locked when calling notifySubscribers. 274 // Any subscriber that is not blocking on it's feed channel will 275 // be skipped. 276 func (c *Controller) notifySubscribers(r record.Record) { 277 c.subscriptionLock.RLock() 278 defer c.subscriptionLock.RUnlock() 279 280 for _, sub := range c.subscriptions { 281 if r.Meta().CheckPermission(sub.local, sub.internal) && sub.q.Matches(r) { 282 select { 283 case sub.Feed <- r: 284 default: 285 } 286 } 287 } 288 } 289 290 func (c *Controller) runPreGetHooks(key string) error { 291 c.hooksLock.RLock() 292 defer c.hooksLock.RUnlock() 293 294 for _, hook := range c.hooks { 295 if !hook.h.UsesPreGet() { 296 continue 297 } 298 299 if !hook.q.MatchesKey(key) { 300 continue 301 } 302 303 if err := hook.h.PreGet(key); err != nil { 304 return err 305 } 306 } 307 308 return nil 309 } 310 311 func (c *Controller) runPostGetHooks(r record.Record) (record.Record, error) { 312 c.hooksLock.RLock() 313 defer c.hooksLock.RUnlock() 314 315 var err error 316 for _, hook := range c.hooks { 317 if !hook.h.UsesPostGet() { 318 continue 319 } 320 321 if !hook.q.Matches(r) { 322 continue 323 } 324 325 r, err = hook.h.PostGet(r) 326 if err != nil { 327 return nil, err 328 } 329 } 330 331 return r, nil 332 } 333 334 func (c *Controller) runPrePutHooks(r record.Record) (record.Record, error) { 335 c.hooksLock.RLock() 336 defer c.hooksLock.RUnlock() 337 338 var err error 339 for _, hook := range c.hooks { 340 if !hook.h.UsesPrePut() { 341 continue 342 } 343 344 if !hook.q.Matches(r) { 345 continue 346 } 347 348 r, err = hook.h.PrePut(r) 349 if err != nil { 350 return nil, err 351 } 352 } 353 354 return r, nil 355 }