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