github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/setting/database.go (about)

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