Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

engine.go 9.7KB

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