You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

engine.go 8.9KB

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