code.gitea.io/gitea@v1.21.7/models/db/engine.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2018 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package db 6 7 import ( 8 "context" 9 "database/sql" 10 "fmt" 11 "io" 12 "reflect" 13 "strings" 14 15 "code.gitea.io/gitea/modules/setting" 16 17 "xorm.io/xorm" 18 "xorm.io/xorm/names" 19 "xorm.io/xorm/schemas" 20 21 _ "github.com/denisenkom/go-mssqldb" // Needed for the MSSQL driver 22 _ "github.com/go-sql-driver/mysql" // Needed for the MySQL driver 23 _ "github.com/lib/pq" // Needed for the Postgresql driver 24 ) 25 26 var ( 27 x *xorm.Engine 28 tables []any 29 initFuncs []func() error 30 ) 31 32 // Engine represents a xorm engine or session. 33 type Engine interface { 34 Table(tableNameOrBean any) *xorm.Session 35 Count(...any) (int64, error) 36 Decr(column string, arg ...any) *xorm.Session 37 Delete(...any) (int64, error) 38 Truncate(...any) (int64, error) 39 Exec(...any) (sql.Result, error) 40 Find(any, ...any) error 41 Get(beans ...any) (bool, error) 42 ID(any) *xorm.Session 43 In(string, ...any) *xorm.Session 44 Incr(column string, arg ...any) *xorm.Session 45 Insert(...any) (int64, error) 46 Iterate(any, xorm.IterFunc) error 47 Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session 48 SQL(any, ...any) *xorm.Session 49 Where(any, ...any) *xorm.Session 50 Asc(colNames ...string) *xorm.Session 51 Desc(colNames ...string) *xorm.Session 52 Limit(limit int, start ...int) *xorm.Session 53 NoAutoTime() *xorm.Session 54 SumInt(bean any, columnName string) (res int64, err error) 55 Sync(...any) error 56 Select(string) *xorm.Session 57 NotIn(string, ...any) *xorm.Session 58 OrderBy(any, ...any) *xorm.Session 59 Exist(...any) (bool, error) 60 Distinct(...string) *xorm.Session 61 Query(...any) ([]map[string][]byte, error) 62 Cols(...string) *xorm.Session 63 Context(ctx context.Context) *xorm.Session 64 Ping() error 65 } 66 67 // TableInfo returns table's information via an object 68 func TableInfo(v any) (*schemas.Table, error) { 69 return x.TableInfo(v) 70 } 71 72 // DumpTables dump tables information 73 func DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error { 74 return x.DumpTables(tables, w, tp...) 75 } 76 77 // RegisterModel registers model, if initfunc provided, it will be invoked after data model sync 78 func RegisterModel(bean any, initFunc ...func() error) { 79 tables = append(tables, bean) 80 if len(initFuncs) > 0 && initFunc[0] != nil { 81 initFuncs = append(initFuncs, initFunc[0]) 82 } 83 } 84 85 func init() { 86 gonicNames := []string{"SSL", "UID"} 87 for _, name := range gonicNames { 88 names.LintGonicMapper[name] = true 89 } 90 } 91 92 // newXORMEngine returns a new XORM engine from the configuration 93 func newXORMEngine() (*xorm.Engine, error) { 94 connStr, err := setting.DBConnStr() 95 if err != nil { 96 return nil, err 97 } 98 99 var engine *xorm.Engine 100 101 if setting.Database.Type.IsPostgreSQL() && len(setting.Database.Schema) > 0 { 102 // OK whilst we sort out our schema issues - create a schema aware postgres 103 registerPostgresSchemaDriver() 104 engine, err = xorm.NewEngine("postgresschema", connStr) 105 } else { 106 engine, err = xorm.NewEngine(setting.Database.Type.String(), connStr) 107 } 108 109 if err != nil { 110 return nil, err 111 } 112 if setting.Database.Type == "mysql" { 113 engine.Dialect().SetParams(map[string]string{"rowFormat": "DYNAMIC"}) 114 } else if setting.Database.Type == "mssql" { 115 engine.Dialect().SetParams(map[string]string{"DEFAULT_VARCHAR": "nvarchar"}) 116 } 117 engine.SetSchema(setting.Database.Schema) 118 return engine, nil 119 } 120 121 // SyncAllTables sync the schemas of all tables, is required by unit test code 122 func SyncAllTables() error { 123 _, err := x.StoreEngine("InnoDB").SyncWithOptions(xorm.SyncOptions{ 124 WarnIfDatabaseColumnMissed: true, 125 }, tables...) 126 return err 127 } 128 129 // InitEngine initializes the xorm.Engine and sets it as db.DefaultContext 130 func InitEngine(ctx context.Context) error { 131 xormEngine, err := newXORMEngine() 132 if err != nil { 133 return fmt.Errorf("failed to connect to database: %w", err) 134 } 135 136 xormEngine.SetMapper(names.GonicMapper{}) 137 // WARNING: for serv command, MUST remove the output to os.stdout, 138 // so use log file to instead print to stdout. 139 xormEngine.SetLogger(NewXORMLogger(setting.Database.LogSQL)) 140 xormEngine.ShowSQL(setting.Database.LogSQL) 141 xormEngine.SetMaxOpenConns(setting.Database.MaxOpenConns) 142 xormEngine.SetMaxIdleConns(setting.Database.MaxIdleConns) 143 xormEngine.SetConnMaxLifetime(setting.Database.ConnMaxLifetime) 144 xormEngine.SetDefaultContext(ctx) 145 146 SetDefaultEngine(ctx, xormEngine) 147 return nil 148 } 149 150 // SetDefaultEngine sets the default engine for db 151 func SetDefaultEngine(ctx context.Context, eng *xorm.Engine) { 152 x = eng 153 DefaultContext = &Context{ 154 Context: ctx, 155 e: x, 156 } 157 } 158 159 // UnsetDefaultEngine closes and unsets the default engine 160 // We hope the SetDefaultEngine and UnsetDefaultEngine can be paired, but it's impossible now, 161 // there are many calls to InitEngine -> SetDefaultEngine directly to overwrite the `x` and DefaultContext without close 162 // Global database engine related functions are all racy and there is no graceful close right now. 163 func UnsetDefaultEngine() { 164 if x != nil { 165 _ = x.Close() 166 x = nil 167 } 168 DefaultContext = nil 169 } 170 171 // InitEngineWithMigration initializes a new xorm.Engine and sets it as the db.DefaultContext 172 // This function must never call .Sync() if the provided migration function fails. 173 // When called from the "doctor" command, the migration function is a version check 174 // that prevents the doctor from fixing anything in the database if the migration level 175 // is different from the expected value. 176 func InitEngineWithMigration(ctx context.Context, migrateFunc func(*xorm.Engine) error) (err error) { 177 if err = InitEngine(ctx); err != nil { 178 return err 179 } 180 181 if err = x.Ping(); err != nil { 182 return err 183 } 184 185 // We have to run migrateFunc here in case the user is re-running installation on a previously created DB. 186 // If we do not then table schemas will be changed and there will be conflicts when the migrations run properly. 187 // 188 // Installation should only be being re-run if users want to recover an old database. 189 // However, we should think carefully about should we support re-install on an installed instance, 190 // as there may be other problems due to secret reinitialization. 191 if err = migrateFunc(x); err != nil { 192 return fmt.Errorf("migrate: %w", err) 193 } 194 195 if err = SyncAllTables(); err != nil { 196 return fmt.Errorf("sync database struct error: %w", err) 197 } 198 199 for _, initFunc := range initFuncs { 200 if err := initFunc(); err != nil { 201 return fmt.Errorf("initFunc failed: %w", err) 202 } 203 } 204 205 return nil 206 } 207 208 // NamesToBean return a list of beans or an error 209 func NamesToBean(names ...string) ([]any, error) { 210 beans := []any{} 211 if len(names) == 0 { 212 beans = append(beans, tables...) 213 return beans, nil 214 } 215 // Need to map provided names to beans... 216 beanMap := make(map[string]any) 217 for _, bean := range tables { 218 219 beanMap[strings.ToLower(reflect.Indirect(reflect.ValueOf(bean)).Type().Name())] = bean 220 beanMap[strings.ToLower(x.TableName(bean))] = bean 221 beanMap[strings.ToLower(x.TableName(bean, true))] = bean 222 } 223 224 gotBean := make(map[any]bool) 225 for _, name := range names { 226 bean, ok := beanMap[strings.ToLower(strings.TrimSpace(name))] 227 if !ok { 228 return nil, fmt.Errorf("no table found that matches: %s", name) 229 } 230 if !gotBean[bean] { 231 beans = append(beans, bean) 232 gotBean[bean] = true 233 } 234 } 235 return beans, nil 236 } 237 238 // DumpDatabase dumps all data from database according the special database SQL syntax to file system. 239 func DumpDatabase(filePath, dbType string) error { 240 var tbs []*schemas.Table 241 for _, t := range tables { 242 t, err := x.TableInfo(t) 243 if err != nil { 244 return err 245 } 246 tbs = append(tbs, t) 247 } 248 249 type Version struct { 250 ID int64 `xorm:"pk autoincr"` 251 Version int64 252 } 253 t, err := x.TableInfo(&Version{}) 254 if err != nil { 255 return err 256 } 257 tbs = append(tbs, t) 258 259 if len(dbType) > 0 { 260 return x.DumpTablesToFile(tbs, filePath, schemas.DBType(dbType)) 261 } 262 return x.DumpTablesToFile(tbs, filePath) 263 } 264 265 // MaxBatchInsertSize returns the table's max batch insert size 266 func MaxBatchInsertSize(bean any) int { 267 t, err := x.TableInfo(bean) 268 if err != nil { 269 return 50 270 } 271 return 999 / len(t.ColumnsSeq()) 272 } 273 274 // IsTableNotEmpty returns true if table has at least one record 275 func IsTableNotEmpty(tableName string) (bool, error) { 276 return x.Table(tableName).Exist() 277 } 278 279 // DeleteAllRecords will delete all the records of this table 280 func DeleteAllRecords(tableName string) error { 281 _, err := x.Exec(fmt.Sprintf("DELETE FROM %s", tableName)) 282 return err 283 } 284 285 // GetMaxID will return max id of the table 286 func GetMaxID(beanOrTableName any) (maxID int64, err error) { 287 _, err = x.Select("MAX(id)").Table(beanOrTableName).Get(&maxID) 288 return maxID, err 289 } 290 291 func SetLogSQL(ctx context.Context, on bool) { 292 e := GetEngine(ctx) 293 if x, ok := e.(*xorm.Engine); ok { 294 x.ShowSQL(on) 295 } else if sess, ok := e.(*xorm.Session); ok { 296 sess.Engine().ShowSQL(on) 297 } 298 }