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  }