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