code.gitea.io/gitea@v1.22.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" 10 "net/url" 11 "os" 12 "path/filepath" 13 "strings" 14 "time" 15 ) 16 17 var ( 18 // SupportedDatabaseTypes includes all XORM supported databases type, sqlite3 maybe added by `database_sqlite3.go` 19 SupportedDatabaseTypes = []string{"mysql", "postgres", "mssql"} 20 // DatabaseTypeNames contains the friendly names for all database types 21 DatabaseTypeNames = map[string]string{"mysql": "MySQL", "postgres": "PostgreSQL", "mssql": "MSSQL", "sqlite3": "SQLite3"} 22 23 // EnableSQLite3 use SQLite3, set by build flag 24 EnableSQLite3 bool 25 26 // Database holds the database settings 27 Database = struct { 28 Type DatabaseType 29 Host string 30 Name string 31 User string 32 Passwd string 33 Schema string 34 SSLMode string 35 Path string 36 LogSQL bool 37 MysqlCharset string 38 CharsetCollation string 39 Timeout int // seconds 40 SQLiteJournalMode string 41 DBConnectRetries int 42 DBConnectBackoff time.Duration 43 MaxIdleConns int 44 MaxOpenConns int 45 ConnMaxLifetime time.Duration 46 IterateBufferSize int 47 AutoMigration bool 48 SlowQueryThreshold time.Duration 49 }{ 50 Timeout: 500, 51 IterateBufferSize: 50, 52 } 53 ) 54 55 // LoadDBSetting loads the database settings 56 func LoadDBSetting() { 57 loadDBSetting(CfgProvider) 58 } 59 60 func loadDBSetting(rootCfg ConfigProvider) { 61 sec := rootCfg.Section("database") 62 Database.Type = DatabaseType(sec.Key("DB_TYPE").String()) 63 64 Database.Host = sec.Key("HOST").String() 65 Database.Name = sec.Key("NAME").String() 66 Database.User = sec.Key("USER").String() 67 if len(Database.Passwd) == 0 { 68 Database.Passwd = sec.Key("PASSWD").String() 69 } 70 Database.Schema = sec.Key("SCHEMA").String() 71 Database.SSLMode = sec.Key("SSL_MODE").MustString("disable") 72 Database.CharsetCollation = sec.Key("CHARSET_COLLATION").String() 73 74 Database.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "gitea.db")) 75 Database.Timeout = sec.Key("SQLITE_TIMEOUT").MustInt(500) 76 Database.SQLiteJournalMode = sec.Key("SQLITE_JOURNAL_MODE").MustString("") 77 78 Database.MaxIdleConns = sec.Key("MAX_IDLE_CONNS").MustInt(2) 79 if Database.Type.IsMySQL() { 80 Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(3 * time.Second) 81 } else { 82 Database.ConnMaxLifetime = sec.Key("CONN_MAX_LIFETIME").MustDuration(0) 83 } 84 Database.MaxOpenConns = sec.Key("MAX_OPEN_CONNS").MustInt(0) 85 86 Database.IterateBufferSize = sec.Key("ITERATE_BUFFER_SIZE").MustInt(50) 87 Database.LogSQL = sec.Key("LOG_SQL").MustBool(false) 88 Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) 89 Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) 90 Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true) 91 Database.SlowQueryThreshold = sec.Key("SLOW_QUERY_THRESHOLD").MustDuration(5 * time.Second) 92 } 93 94 // DBConnStr returns database connection string 95 func DBConnStr() (string, error) { 96 var connStr string 97 paramSep := "?" 98 if strings.Contains(Database.Name, paramSep) { 99 paramSep = "&" 100 } 101 switch Database.Type { 102 case "mysql": 103 connType := "tcp" 104 if len(Database.Host) > 0 && Database.Host[0] == '/' { // looks like a unix socket 105 connType = "unix" 106 } 107 tls := Database.SSLMode 108 if tls == "disable" { // allow (Postgres-inspired) default value to work in MySQL 109 tls = "false" 110 } 111 connStr = fmt.Sprintf("%s:%s@%s(%s)/%s%sparseTime=true&tls=%s", 112 Database.User, Database.Passwd, connType, Database.Host, Database.Name, paramSep, tls) 113 case "postgres": 114 connStr = getPostgreSQLConnectionString(Database.Host, Database.User, Database.Passwd, Database.Name, Database.SSLMode) 115 case "mssql": 116 host, port := ParseMSSQLHostPort(Database.Host) 117 connStr = fmt.Sprintf("server=%s; port=%s; database=%s; user id=%s; password=%s;", host, port, Database.Name, Database.User, Database.Passwd) 118 case "sqlite3": 119 if !EnableSQLite3 { 120 return "", errors.New("this Gitea binary was not built with SQLite3 support") 121 } 122 if err := os.MkdirAll(filepath.Dir(Database.Path), os.ModePerm); err != nil { 123 return "", fmt.Errorf("Failed to create directories: %w", err) 124 } 125 journalMode := "" 126 if Database.SQLiteJournalMode != "" { 127 journalMode = "&_journal_mode=" + Database.SQLiteJournalMode 128 } 129 connStr = fmt.Sprintf("file:%s?cache=shared&mode=rwc&_busy_timeout=%d&_txlock=immediate%s", 130 Database.Path, Database.Timeout, journalMode) 131 default: 132 return "", fmt.Errorf("unknown database type: %s", Database.Type) 133 } 134 135 return connStr, nil 136 } 137 138 // parsePostgreSQLHostPort parses given input in various forms defined in 139 // https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING 140 // and returns proper host and port number. 141 func parsePostgreSQLHostPort(info string) (host, port string) { 142 if h, p, err := net.SplitHostPort(info); err == nil { 143 host, port = h, p 144 } else { 145 // treat the "info" as "host", if it's an IPv6 address, remove the wrapper 146 host = info 147 if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { 148 host = host[1 : len(host)-1] 149 } 150 } 151 152 // set fallback values 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, dbsslMode string) (connStr string) { 163 dbName, dbParam, _ := strings.Cut(dbName, "?") 164 host, port := parsePostgreSQLHostPort(dbHost) 165 connURL := url.URL{ 166 Scheme: "postgres", 167 User: url.UserPassword(dbUser, dbPasswd), 168 Host: net.JoinHostPort(host, port), 169 Path: dbName, 170 OmitHost: false, 171 RawQuery: dbParam, 172 } 173 query := connURL.Query() 174 if strings.HasPrefix(host, "/") { // looks like a unix socket 175 query.Add("host", host) 176 connURL.Host = ":" + port 177 } 178 query.Set("sslmode", dbsslMode) 179 connURL.RawQuery = query.Encode() 180 return connURL.String() 181 } 182 183 // ParseMSSQLHostPort splits the host into host and port 184 func ParseMSSQLHostPort(info string) (string, string) { 185 // the default port "0" might be related to MSSQL's dynamic port, maybe it should be double-confirmed in the future 186 host, port := "127.0.0.1", "0" 187 if strings.Contains(info, ":") { 188 host = strings.Split(info, ":")[0] 189 port = strings.Split(info, ":")[1] 190 } else if strings.Contains(info, ",") { 191 host = strings.Split(info, ",")[0] 192 port = strings.TrimSpace(strings.Split(info, ",")[1]) 193 } else if len(info) > 0 { 194 host = info 195 } 196 if host == "" { 197 host = "127.0.0.1" 198 } 199 if port == "" { 200 port = "0" 201 } 202 return host, port 203 } 204 205 type DatabaseType string 206 207 func (t DatabaseType) String() string { 208 return string(t) 209 } 210 211 func (t DatabaseType) IsSQLite3() bool { 212 return t == "sqlite3" 213 } 214 215 func (t DatabaseType) IsMySQL() bool { 216 return t == "mysql" 217 } 218 219 func (t DatabaseType) IsMSSQL() bool { 220 return t == "mssql" 221 } 222 223 func (t DatabaseType) IsPostgreSQL() bool { 224 return t == "postgres" 225 }