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