Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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