github.com/pjdufour-truss/pop@v4.11.2-0.20190705085848-4c90b0ff4d5a+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 Options map[string]string 45 // Query string encoded options from URL. Example: "sslmode=disable" 46 RawOptions string 47 } 48 49 var dialectX = regexp.MustCompile(`\S+://`) 50 51 // withURL parses and overrides all connection details with values 52 // from standard URL except Dialect. It also calls dialect specific 53 // URL parser if exists. 54 func (cd *ConnectionDetails) withURL() error { 55 ul := cd.URL 56 if cd.Dialect == "" { 57 if dialectX.MatchString(ul) { 58 // Guess the dialect from the scheme 59 dialect := ul[:strings.Index(ul, ":")] 60 cd.Dialect = normalizeSynonyms(dialect) 61 } else { 62 return errors.New("no dialect provided, and could not guess it from URL") 63 } 64 } else if !dialectX.MatchString(ul) { 65 ul = cd.Dialect + "://" + ul 66 } 67 68 if !DialectSupported(cd.Dialect) { 69 return errors.Errorf("unsupported dialect '%s'", cd.Dialect) 70 } 71 72 // warning message is required to prevent confusion 73 // even though this behavior was documented. 74 if cd.Database+cd.Host+cd.Port+cd.User+cd.Password != "" { 75 log(logging.Warn, "One or more of connection details are specified in database.yml. Override them with values in URL.") 76 } 77 78 if up, ok := urlParser[cd.Dialect]; ok { 79 return up(cd) 80 } 81 82 // Fallback on generic parsing if no URL parser was found for the dialect. 83 u, err := url.Parse(ul) 84 if err != nil { 85 return errors.Wrapf(err, "couldn't parse %s", ul) 86 } 87 cd.Database = strings.TrimPrefix(u.Path, "/") 88 89 hp := strings.Split(u.Host, ":") 90 cd.Host = hp[0] 91 if len(hp) > 1 { 92 cd.Port = hp[1] 93 } 94 95 if u.User != nil { 96 cd.User = u.User.Username() 97 cd.Password, _ = u.User.Password() 98 } 99 cd.RawOptions = u.RawQuery 100 101 return nil 102 } 103 104 // Finalize cleans up the connection details by normalizing names, 105 // filling in default values, etc... 106 func (cd *ConnectionDetails) Finalize() error { 107 cd.Dialect = normalizeSynonyms(cd.Dialect) 108 109 if cd.Options == nil { // for safety 110 cd.Options = make(map[string]string) 111 } 112 113 // Process the database connection string, if provided. 114 if cd.URL != "" { 115 if err := cd.withURL(); err != nil { 116 return err 117 } 118 } 119 120 if fin, ok := finalizer[cd.Dialect]; ok { 121 fin(cd) 122 } 123 124 if DialectSupported(cd.Dialect) { 125 if cd.Database != "" || cd.URL != "" { 126 return nil 127 } 128 return errors.New("no database or URL specified") 129 } 130 return errors.Errorf("unsupported dialect '%v'", cd.Dialect) 131 } 132 133 // Parse cleans up the connection details by normalizing names, 134 // filling in default values, etc... 135 // Deprecated: use ConnectionDetails.Finalize() instead. 136 func (cd *ConnectionDetails) Parse(port string) error { 137 oncer.Deprecate(0, "pop.ConnectionDetails#Parse", "pop.ConnectionDetails#Finalize") 138 return cd.Finalize() 139 } 140 141 // RetrySleep returns the amount of time to wait between two connection retries 142 func (cd *ConnectionDetails) RetrySleep() time.Duration { 143 d, err := time.ParseDuration(defaults.String(cd.Options["retry_sleep"], "1ms")) 144 if err != nil { 145 return 1 * time.Millisecond 146 } 147 return d 148 } 149 150 // RetryLimit returns the maximum number of accepted connection retries 151 func (cd *ConnectionDetails) RetryLimit() int { 152 i, err := strconv.Atoi(defaults.String(cd.Options["retry_limit"], "1000")) 153 if err != nil { 154 return 100 155 } 156 return i 157 } 158 159 // MigrationTableName returns the name of the table to track migrations 160 func (cd *ConnectionDetails) MigrationTableName() string { 161 return defaults.String(cd.Options["migration_table_name"], "schema_migration") 162 } 163 164 // OptionsString returns URL parameter encoded string from options. 165 func (cd *ConnectionDetails) OptionsString(s string) string { 166 if cd.RawOptions != "" { 167 return cd.RawOptions 168 } 169 if cd.Options != nil { 170 for k, v := range cd.Options { 171 s = fmt.Sprintf("%s&%s=%s", s, k, v) 172 } 173 } 174 return strings.TrimLeft(s, "&") 175 }