github.com/rjgonzale/pop/v5@v5.1.3-dev/connection_details.go (about) 1 package pop 2 3 import ( 4 "fmt" 5 "net/url" 6 "regexp" 7 "strconv" 8 "strings" 9 "time" 10 11 "github.com/gobuffalo/pop/v5/internal/defaults" 12 "github.com/gobuffalo/pop/v5/logging" 13 "github.com/pkg/errors" 14 ) 15 16 // ConnectionDetails stores the data needed to connect to a datasource 17 type ConnectionDetails struct { 18 // Dialect is the pop dialect to use. Example: "postgres" or "sqlite3" or "mysql" 19 Dialect string 20 // Driver specifies the database driver to use (optional) 21 Driver string 22 // The name of your database. Example: "foo_development" 23 Database string 24 // The host of your database. Example: "127.0.0.1" 25 Host string 26 // The port of your database. Example: 1234 27 // Will default to the "default" port for each dialect. 28 Port string 29 // The username of the database user. Example: "root" 30 User string 31 // The password of the database user. Example: "password" 32 Password string 33 // The encoding to use to create the database and communicate with it. 34 Encoding string 35 // Instead of specifying each individual piece of the 36 // connection you can instead just specify the URL of the 37 // database. Example: "postgres://postgres:postgres@localhost:5432/pop_test?sslmode=disable" 38 URL string 39 // Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetMaxOpenConns 40 Pool int 41 // Defaults to 2. See https://golang.org/pkg/database/sql/#DB.SetMaxIdleConns 42 IdlePool int 43 // Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetConnMaxLifetime 44 ConnMaxLifetime time.Duration 45 // Defaults to `false`. See https://godoc.org/github.com/jmoiron/sqlx#DB.Unsafe 46 Unsafe bool 47 Options map[string]string 48 // Query string encoded options from URL. Example: "sslmode=disable" 49 RawOptions string 50 } 51 52 var dialectX = regexp.MustCompile(`\S+://`) 53 54 // withURL parses and overrides all connection details with values 55 // from standard URL except Dialect. It also calls dialect specific 56 // URL parser if exists. 57 func (cd *ConnectionDetails) withURL() error { 58 ul := cd.URL 59 if cd.Dialect == "" { 60 if dialectX.MatchString(ul) { 61 // Guess the dialect from the scheme 62 dialect := ul[:strings.Index(ul, ":")] 63 cd.Dialect = normalizeSynonyms(dialect) 64 } else { 65 return errors.New("no dialect provided, and could not guess it from URL") 66 } 67 } else if !dialectX.MatchString(ul) { 68 ul = cd.Dialect + "://" + ul 69 } 70 71 if !DialectSupported(cd.Dialect) { 72 return errors.Errorf("unsupported dialect '%s'", cd.Dialect) 73 } 74 75 // warning message is required to prevent confusion 76 // even though this behavior was documented. 77 if cd.Database+cd.Host+cd.Port+cd.User+cd.Password != "" { 78 log(logging.Warn, "One or more of connection details are specified in database.yml. Override them with values in URL.") 79 } 80 81 if up, ok := urlParser[cd.Dialect]; ok { 82 return up(cd) 83 } 84 85 // Fallback on generic parsing if no URL parser was found for the dialect. 86 u, err := url.Parse(ul) 87 if err != nil { 88 return errors.Wrapf(err, "couldn't parse %s", ul) 89 } 90 cd.Database = strings.TrimPrefix(u.Path, "/") 91 92 hp := strings.Split(u.Host, ":") 93 cd.Host = hp[0] 94 if len(hp) > 1 { 95 cd.Port = hp[1] 96 } 97 98 if u.User != nil { 99 cd.User = u.User.Username() 100 cd.Password, _ = u.User.Password() 101 } 102 cd.RawOptions = u.RawQuery 103 104 return nil 105 } 106 107 // Finalize cleans up the connection details by normalizing names, 108 // filling in default values, etc... 109 func (cd *ConnectionDetails) Finalize() error { 110 cd.Dialect = normalizeSynonyms(cd.Dialect) 111 112 if cd.Options == nil { // for safety 113 cd.Options = make(map[string]string) 114 } 115 116 // Process the database connection string, if provided. 117 if cd.URL != "" { 118 if err := cd.withURL(); err != nil { 119 return err 120 } 121 } 122 123 if fin, ok := finalizer[cd.Dialect]; ok { 124 fin(cd) 125 } 126 127 if DialectSupported(cd.Dialect) { 128 if cd.Database != "" || cd.URL != "" { 129 return nil 130 } 131 return errors.New("no database or URL specified") 132 } 133 return errors.Errorf("unsupported dialect '%v'", cd.Dialect) 134 } 135 136 // RetrySleep returns the amount of time to wait between two connection retries 137 func (cd *ConnectionDetails) RetrySleep() time.Duration { 138 d, err := time.ParseDuration(defaults.String(cd.Options["retry_sleep"], "1ms")) 139 if err != nil { 140 return 1 * time.Millisecond 141 } 142 return d 143 } 144 145 // RetryLimit returns the maximum number of accepted connection retries 146 func (cd *ConnectionDetails) RetryLimit() int { 147 i, err := strconv.Atoi(defaults.String(cd.Options["retry_limit"], "1000")) 148 if err != nil { 149 return 100 150 } 151 return i 152 } 153 154 // MigrationTableName returns the name of the table to track migrations 155 func (cd *ConnectionDetails) MigrationTableName() string { 156 return defaults.String(cd.Options["migration_table_name"], "schema_migration") 157 } 158 159 // OptionsString returns URL parameter encoded string from options. 160 func (cd *ConnectionDetails) OptionsString(s string) string { 161 if cd.RawOptions != "" { 162 return cd.RawOptions 163 } 164 if cd.Options != nil { 165 for k, v := range cd.Options { 166 s = fmt.Sprintf("%s&%s=%s", s, k, v) 167 } 168 } 169 return strings.TrimLeft(s, "&") 170 }