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  }