code.gitea.io/gitea@v1.21.7/models/db/context.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package db 5 6 import ( 7 "context" 8 "database/sql" 9 10 "xorm.io/builder" 11 "xorm.io/xorm" 12 ) 13 14 // DefaultContext is the default context to run xorm queries in 15 // will be overwritten by Init with HammerContext 16 var DefaultContext context.Context 17 18 // contextKey is a value for use with context.WithValue. 19 type contextKey struct { 20 name string 21 } 22 23 // enginedContextKey is a context key. It is used with context.Value() to get the current Engined for the context 24 var ( 25 enginedContextKey = &contextKey{"engined"} 26 _ Engined = &Context{} 27 ) 28 29 // Context represents a db context 30 type Context struct { 31 context.Context 32 e Engine 33 transaction bool 34 } 35 36 func newContext(ctx context.Context, e Engine, transaction bool) *Context { 37 return &Context{ 38 Context: ctx, 39 e: e, 40 transaction: transaction, 41 } 42 } 43 44 // InTransaction if context is in a transaction 45 func (ctx *Context) InTransaction() bool { 46 return ctx.transaction 47 } 48 49 // Engine returns db engine 50 func (ctx *Context) Engine() Engine { 51 return ctx.e 52 } 53 54 // Value shadows Value for context.Context but allows us to get ourselves and an Engined object 55 func (ctx *Context) Value(key any) any { 56 if key == enginedContextKey { 57 return ctx 58 } 59 return ctx.Context.Value(key) 60 } 61 62 // WithContext returns this engine tied to this context 63 func (ctx *Context) WithContext(other context.Context) *Context { 64 return newContext(ctx, ctx.e.Context(other), ctx.transaction) 65 } 66 67 // Engined structs provide an Engine 68 type Engined interface { 69 Engine() Engine 70 } 71 72 // GetEngine will get a db Engine from this context or return an Engine restricted to this context 73 func GetEngine(ctx context.Context) Engine { 74 if e := getEngine(ctx); e != nil { 75 return e 76 } 77 return x.Context(ctx) 78 } 79 80 // getEngine will get a db Engine from this context or return nil 81 func getEngine(ctx context.Context) Engine { 82 if engined, ok := ctx.(Engined); ok { 83 return engined.Engine() 84 } 85 enginedInterface := ctx.Value(enginedContextKey) 86 if enginedInterface != nil { 87 return enginedInterface.(Engined).Engine() 88 } 89 return nil 90 } 91 92 // Committer represents an interface to Commit or Close the Context 93 type Committer interface { 94 Commit() error 95 Close() error 96 } 97 98 // halfCommitter is a wrapper of Committer. 99 // It can be closed early, but can't be committed early, it is useful for reusing a transaction. 100 type halfCommitter struct { 101 committer Committer 102 committed bool 103 } 104 105 func (c *halfCommitter) Commit() error { 106 c.committed = true 107 // should do nothing, and the parent committer will commit later 108 return nil 109 } 110 111 func (c *halfCommitter) Close() error { 112 if c.committed { 113 // it's "commit and close", should do nothing, and the parent committer will commit later 114 return nil 115 } 116 117 // it's "rollback and close", let the parent committer rollback right now 118 return c.committer.Close() 119 } 120 121 // TxContext represents a transaction Context, 122 // it will reuse the existing transaction in the parent context or create a new one. 123 func TxContext(parentCtx context.Context) (*Context, Committer, error) { 124 if sess, ok := inTransaction(parentCtx); ok { 125 return newContext(parentCtx, sess, true), &halfCommitter{committer: sess}, nil 126 } 127 128 sess := x.NewSession() 129 if err := sess.Begin(); err != nil { 130 sess.Close() 131 return nil, nil, err 132 } 133 134 return newContext(DefaultContext, sess, true), sess, nil 135 } 136 137 // WithTx represents executing database operations on a transaction, if the transaction exist, 138 // this function will reuse it otherwise will create a new one and close it when finished. 139 func WithTx(parentCtx context.Context, f func(ctx context.Context) error) error { 140 if sess, ok := inTransaction(parentCtx); ok { 141 err := f(newContext(parentCtx, sess, true)) 142 if err != nil { 143 // rollback immediately, in case the caller ignores returned error and tries to commit the transaction. 144 _ = sess.Close() 145 } 146 return err 147 } 148 return txWithNoCheck(parentCtx, f) 149 } 150 151 func txWithNoCheck(parentCtx context.Context, f func(ctx context.Context) error) error { 152 sess := x.NewSession() 153 defer sess.Close() 154 if err := sess.Begin(); err != nil { 155 return err 156 } 157 158 if err := f(newContext(parentCtx, sess, true)); err != nil { 159 return err 160 } 161 162 return sess.Commit() 163 } 164 165 // Insert inserts records into database 166 func Insert(ctx context.Context, beans ...any) error { 167 _, err := GetEngine(ctx).Insert(beans...) 168 return err 169 } 170 171 // Exec executes a sql with args 172 func Exec(ctx context.Context, sqlAndArgs ...any) (sql.Result, error) { 173 return GetEngine(ctx).Exec(sqlAndArgs...) 174 } 175 176 // GetByBean filled empty fields of the bean according non-empty fields to query in database. 177 func GetByBean(ctx context.Context, bean any) (bool, error) { 178 return GetEngine(ctx).Get(bean) 179 } 180 181 func Exist[T any](ctx context.Context, cond builder.Cond) (bool, error) { 182 if !cond.IsValid() { 183 return false, ErrConditionRequired{} 184 } 185 186 var bean T 187 return GetEngine(ctx).Where(cond).NoAutoCondition().Exist(&bean) 188 } 189 190 // DeleteByBean deletes all records according non-empty fields of the bean as conditions. 191 func DeleteByBean(ctx context.Context, bean any) (int64, error) { 192 return GetEngine(ctx).Delete(bean) 193 } 194 195 // DeleteByID deletes the given bean with the given ID 196 func DeleteByID(ctx context.Context, id int64, bean any) (int64, error) { 197 return GetEngine(ctx).ID(id).NoAutoCondition().NoAutoTime().Delete(bean) 198 } 199 200 // FindIDs finds the IDs for the given table name satisfying the given condition 201 // By passing a different value than "id" for "idCol", you can query for foreign IDs, i.e. the repo IDs which satisfy the condition 202 func FindIDs(ctx context.Context, tableName, idCol string, cond builder.Cond) ([]int64, error) { 203 ids := make([]int64, 0, 10) 204 if err := GetEngine(ctx).Table(tableName). 205 Cols(idCol). 206 Where(cond). 207 Find(&ids); err != nil { 208 return nil, err 209 } 210 return ids, nil 211 } 212 213 // DecrByIDs decreases the given column for entities of the "bean" type with one of the given ids by one 214 // Timestamps of the entities won't be updated 215 func DecrByIDs(ctx context.Context, ids []int64, decrCol string, bean any) error { 216 _, err := GetEngine(ctx).Decr(decrCol).In("id", ids).NoAutoCondition().NoAutoTime().Update(bean) 217 return err 218 } 219 220 // DeleteBeans deletes all given beans, beans must contain delete conditions. 221 func DeleteBeans(ctx context.Context, beans ...any) (err error) { 222 e := GetEngine(ctx) 223 for i := range beans { 224 if _, err = e.Delete(beans[i]); err != nil { 225 return err 226 } 227 } 228 return nil 229 } 230 231 // TruncateBeans deletes all given beans, beans may contain delete conditions. 232 func TruncateBeans(ctx context.Context, beans ...any) (err error) { 233 e := GetEngine(ctx) 234 for i := range beans { 235 if _, err = e.Truncate(beans[i]); err != nil { 236 return err 237 } 238 } 239 return nil 240 } 241 242 // CountByBean counts the number of database records according non-empty fields of the bean as conditions. 243 func CountByBean(ctx context.Context, bean any) (int64, error) { 244 return GetEngine(ctx).Count(bean) 245 } 246 247 // TableName returns the table name according a bean object 248 func TableName(bean any) string { 249 return x.TableName(bean) 250 } 251 252 // InTransaction returns true if the engine is in a transaction otherwise return false 253 func InTransaction(ctx context.Context) bool { 254 _, ok := inTransaction(ctx) 255 return ok 256 } 257 258 func inTransaction(ctx context.Context) (*xorm.Session, bool) { 259 e := getEngine(ctx) 260 if e == nil { 261 return nil, false 262 } 263 264 switch t := e.(type) { 265 case *xorm.Engine: 266 return nil, false 267 case *xorm.Session: 268 if t.IsInTx() { 269 return t, true 270 } 271 return nil, false 272 default: 273 return nil, false 274 } 275 }