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

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