go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tq/internal/db/db.go (about) 1 // Copyright 2020 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package db defines common database interface. 16 package db 17 18 import ( 19 "context" 20 "fmt" 21 22 "go.chromium.org/luci/server/module" 23 "go.chromium.org/luci/server/tq/internal/reminder" 24 ) 25 26 // DB abstracts out specific storage implementation. 27 type DB interface { 28 // Kind identifies this particular database implementation. 29 // 30 // Among other things it is used in Cloud Tasks messages involved in the 31 // implementation of the distributed sweeping. 32 Kind() string 33 34 // Defer defers the execution of the callback until the transaction lands. 35 // 36 // Panics if called outside of a transaction. 37 // 38 // The callback will receive the original non-transactional context. 39 Defer(context.Context, func(context.Context)) 40 41 // SaveReminder persists reminder in a transaction context. 42 // 43 // Tags retriable errors as transient. 44 SaveReminder(context.Context, *reminder.Reminder) error 45 46 // DeleteReminder deletes reminder in a non-transaction context. 47 DeleteReminder(context.Context, *reminder.Reminder) error 48 49 // FetchRemindersMeta fetches Reminders with Ids in [low..high) range. 50 // 51 // RawPayload of Reminders should not be fetched. 52 // Both fresh & stale reminders should be fetched. 53 // The reminders should be returned in order of ascending Id. 54 // 55 // In case of error, partial result of fetched Reminders so far should be 56 // returned alongside the error. The caller will later call this method again 57 // to fetch the remaining of Reminders in range of [<lastReturned.Id+1> .. high). 58 FetchRemindersMeta(ctx context.Context, low, high string, limit int) ([]*reminder.Reminder, error) 59 60 // FetchReminderRawPayloads fetches raw payloads of a batch of Reminders. 61 // 62 // The Reminder objects are re-used in the returned batch. 63 // If any Reminder is no longer found, it is silently omitted in the returned 64 // batch. 65 // In case of any other error, partial result of fetched Reminders so far 66 // should be returned alongside the error. 67 FetchReminderRawPayloads(context.Context, []*reminder.Reminder) ([]*reminder.Reminder, error) 68 } 69 70 // Impl knows how to instantiate DB instances. 71 type Impl struct { 72 // Kind identifies this particular DB implementation. 73 // 74 // Must match Kind() of the produced DB instance. 75 Kind string 76 77 // Module is name of the server module with DB implementation, if any. 78 Module module.Name 79 80 // ProbeForTxn "probes" a context for an active transaction, returning a DB 81 // that can be used to transactionally submit reminders or nil if this is not 82 // a transactional context. 83 ProbeForTxn func(context.Context) DB 84 85 // NonTxn returns an instance of DB that can be used outside of 86 // transactions. 87 // 88 // This is used by the sweeper to enumerate reminders. 89 NonTxn func(context.Context) DB 90 } 91 92 var impls []Impl 93 94 // Register registers a database implementation. 95 // 96 // Must be called during init() time. 97 func Register(db Impl) { 98 if db.Kind == "" { 99 panic("Kind must not be empty") 100 } 101 for _, impl := range impls { 102 if impl.Kind == db.Kind { 103 panic(fmt.Sprintf("DB %q is already registered", db.Kind)) 104 } 105 } 106 impls = append(impls, db) 107 } 108 109 // Configured returns true if there's at least one registered implementation. 110 func Configured() bool { 111 return len(impls) > 0 112 } 113 114 // Kinds returns IDs of registered database implementations. 115 func Kinds() []string { 116 kinds := make([]string, len(impls)) 117 for i, impl := range impls { 118 kinds[i] = impl.Kind 119 } 120 return kinds 121 } 122 123 // VisitImpls calls the callback for all registered implementation. 124 func VisitImpls(cb func(db *Impl)) { 125 for i := range impls { 126 cb(&impls[i]) 127 } 128 } 129 130 // TxnDB returns a Database that matches the context or nil. 131 // 132 // The process has a list of database engines registered via Register. Given 133 // a context, TxnDB examines if it carries a transaction with any of the 134 // registered DBs. 135 // 136 // Panics if more than one database matches the context. 137 func TxnDB(ctx context.Context) (db DB) { 138 for _, impl := range impls { 139 if d := impl.ProbeForTxn(ctx); d != nil { 140 if db != nil { 141 panic(fmt.Sprintf("multiple databases match the context: %q and %q", db.Kind(), d.Kind())) 142 } 143 db = d 144 } 145 } 146 return 147 } 148 149 // NonTxnDB returns a database with given ID or nil if not registered. 150 func NonTxnDB(ctx context.Context, id string) DB { 151 for _, impl := range impls { 152 if impl.Kind == id { 153 return impl.NonTxn(ctx) 154 } 155 } 156 return nil 157 }