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.

database.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package setting
  4. import (
  5. "errors"
  6. "fmt"
  7. "net/url"
  8. "os"
  9. "path"
  10. "path/filepath"
  11. "strings"
  12. "time"
  13. "code.gitea.io/gitea/modules/log"
  14. )
  15. var (
  16. // SupportedDatabaseTypes includes all XORM supported databases type, sqlite3 maybe added by `database_sqlite3.go`
  17. SupportedDatabaseTypes = []string{"mysql", "postgres", "mssql"}
  18. // DatabaseTypeNames contains the friendly names for all database types
  19. DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "mssql": "MSSQL", "sqlite3": "SQLite3"}
  20. // EnableSQLite3 use SQLite3, set by build flag
  21. EnableSQLite3 bool
  22. // Database holds the database settings
  23. Database = struct {
  24. Type string
  25. Host string
  26. Name string
  27. User string
  28. Passwd string
  29. Schema string
  30. SSLMode string
  31. Path string
  32. LogSQL bool
  33. Charset string
  34. Timeout int // seconds
  35. SQLiteJournalMode string
  36. UseSQLite3 bool
  37. UseMySQL bool
  38. UseMSSQL bool
  39. UsePostgreSQL bool
  40. DBConnectRetries int
  41. DBConnectBackoff time.Duration
  42. MaxIdleConns int
  43. MaxOpenConns int
  44. ConnMaxLifetime time.Duration
  45. IterateBufferSize int
  46. AutoMigration bool
  47. }{
  48. Timeout: 500,
  49. IterateBufferSize: 50,
  50. }
  51. )
  52. // InitDBConfig loads the database settings
  53. func InitDBConfig() {
  54. sec := Cfg.Section("database")
  55. Database.Type = sec.Key("DB_TYPE").String()
  56. defaultCharset := "utf8"
  57. Database.UseMySQL = false
  58. Database.UseSQLite3 = false
  59. Database.UsePostgreSQL = false
  60. Database.UseMSSQL = false
  61. switch Database.Type {
  62. case "sqlite3":
  63. Database.UseSQLite3 = true
  64. case "mysql":
  65. Database.UseMySQL = true
  66. defaultCharset = "utf8mb4"
  67. case "postgres":
  68. Database.UsePostgreSQL = true
  69. case "mssql":
  70. Database.UseMSSQL = true
  71. }
  72. Database.Host = sec.Key("HOST").String()
  73. Database.Name = sec.Key("NAME").String()
  74. Database.User = sec.Key("USER").String()
  75. if len(Database.Passwd) == 0 {
  76. Database.Passwd = sec.Key("PASSWD").String()
  77. }
  78. Database.Schema = sec.Key("SCHEMA").String()
  79. Database.SSLMode = sec.Key("SSL_MODE").MustString("disable")
  80. Database.Charset = sec.Key("CHARSET").In(defaultCharset, []string{"utf8", "utf8mb4"})
  81. if Database.UseMySQL && defaultCharset != "utf8mb4" {
  82. log.Error("Deprecated database mysql charset utf8 support, please use utf8mb4 or convert utf8 to utf8mb4.")
  83. }
  84. Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db"))
  85. Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500)
  86. Database.SQLiteJournalMode = sec.Key("SQLITE_JOURNAL_MODE").MustString("")
  87. Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2)
  88. if Database.UseMySQL {
  89. Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(3 * time.Second)
  90. } else {
  91. Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0)
  92. }
  93. Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0)
  94. Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50)
  95. Database.LogSQL = sec.Key("LOG_SQL").MustBool(true)
  96. Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10)
  97. Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second)
  98. Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true)
  99. }
  100. // DBConnStr returns database connection string
  101. func DBConnStr() (string, error) {
  102. var connStr string
  103. Param := "?"
  104. if strings.Contains(Database.Name, Param) {
  105. Param = "&"
  106. }
  107. switch Database.Type {
  108. case "mysql":
  109. connType := "tcp"
  110. if len(Database.Host) > 0 && Database.Host[0] == '/' { // looks like a unix socket
  111. connType = "unix"
  112. }
  113. tls := Database.SSLMode
  114. if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL
  115. tls = "false"
  116. }
  117. connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%scharset=%s&parseTime=true&tls=%s",
  118. Database.User, Database.Passwd, connType, Database.Host, Database.Name, Param, Database.Charset, tls)
  119. case "postgres":
  120. connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Param, Database.SSLMode)
  121. case "mssql":
  122. host, port := ParseMSSQLHostPort(Database.Host)
  123. connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd)
  124. case "sqlite3":
  125. if !EnableSQLite3 {
  126. return "", errors.New("this binary version does not build support for SQLite3")
  127. }
  128. if err := os.MkdirAll(path.Dir(Database.Path), os.ModePerm); err != nil {
  129. return "", fmt.Errorf("Failed to create directories: %w", err)
  130. }
  131. journalMode := ""
  132. if Database.SQLiteJournalMode != "" {
  133. journalMode = "&_journal_mode=" + Database.SQLiteJournalMode
  134. }
  135. connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s",
  136. Database.Path, Database.Timeout, journalMode)
  137. default:
  138. return "", fmt.Errorf("Unknown database type: %s", Database.Type)
  139. }
  140. return connStr, nil
  141. }
  142. // parsePostgreSQLHostPort parses given input in various forms defined in
  143. // https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
  144. // and returns proper host and port number.
  145. func parsePostgreSQLHostPort(info string) (string, string) {
  146. host, port := "127.0.0.1", "5432"
  147. if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
  148. idx := strings.LastIndex(info, ":")
  149. host = info[:idx]
  150. port = info[idx+1:]
  151. } else if len(info) > 0 {
  152. host = info
  153. }
  154. if host == "" {
  155. host = "127.0.0.1"
  156. }
  157. if port == "" {
  158. port = "5432"
  159. }
  160. return host, port
  161. }
  162. func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbParam, dbsslMode string) (connStr string) {
  163. host, port := parsePostgreSQLHostPort(dbHost)
  164. if host[0] == '/' { // looks like a unix socket
  165. connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&host=%s",
  166. url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbsslMode, host)
  167. } else {
  168. connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s",
  169. url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbsslMode)
  170. }
  171. return connStr
  172. }
  173. // ParseMSSQLHostPort splits the host into host and port
  174. func ParseMSSQLHostPort(info string) (string, string) {
  175. // the default port "0" might be related to MSSQL's dynamic port, maybe it should be double-confirmed in the future
  176. host, port := "127.0.0.1", "0"
  177. if strings.Contains(info, ":") {
  178. host = strings.Split(info, ":")[0]
  179. port = strings.Split(info, ":")[1]
  180. } else if strings.Contains(info, ",") {
  181. host = strings.Split(info, ",")[0]
  182. port = strings.TrimSpace(strings.Split(info, ",")[1])
  183. } else if len(info) > 0 {
  184. host = info
  185. }
  186. if host == "" {
  187. host = "127.0.0.1"
  188. }
  189. if port == "" {
  190. port = "0"
  191. }
  192. return host, port
  193. }