github.com/dkishere/pop/v6@v6.103.1/connection_details.go (about) 1 package pop 2 3 import ( 4 "errors" 5 "fmt" 6 "net/url" 7 "regexp" 8 "strconv" 9 "strings" 10 "time" 11 12 "github.com/dkishere/pop/v6/internal/defaults" 13 "github.com/dkishere/pop/v6/logging" 14 "github.com/luna-duclos/instrumentedsql" 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 // Defaults to 0 "unlimited". See https://golang.org/pkg/database/sql/#DB.SetConnMaxIdleTime 47 ConnMaxIdleTime time.Duration 48 // Defaults to `false`. See https://godoc.org/github.com/jmoiron/sqlx#DB.Unsafe 49 Unsafe bool 50 Options map[string]string 51 // Query string encoded options from URL. Example: "sslmode=disable" 52 RawOptions string 53 // UseInstrumentedDriver if set to true uses a wrapper for the underlying driver which exposes tracing 54 // information in the Open Tracing, Open Census, Google, and AWS Xray format. This is useful when using 55 // tracing with Jaeger, DataDog, Zipkin, or other tracing software. 56 UseInstrumentedDriver bool 57 // InstrumentedDriverOptions sets the options for the instrumented driver. These options are empty by default meaning 58 // that instrumentation is disabled. 59 // 60 // For more information check out the docs at https://github.com/luna-duclos/instrumentedsql. If you use Open Tracing, these options 61 // could looks as follows: 62 // 63 // InstrumentedDriverOptions: []instrumentedsql.Opt{instrumentedsql.WithTracer(opentracing.NewTracer(true))} 64 // 65 // It is also recommended to include `instrumentedsql.WithOmitArgs()` which prevents SQL arguments (e.g. passwords) 66 // from being traced or logged. 67 InstrumentedDriverOptions []instrumentedsql.Opt 68 } 69 70 var dialectX = regexp.MustCompile(`\S+://`) 71 72 // withURL parses and overrides all connection details with values 73 // from standard URL except Dialect. It also calls dialect specific 74 // URL parser if exists. 75 func (cd *ConnectionDetails) withURL() error { 76 ul := cd.URL 77 if cd.Dialect == "" { 78 if dialectX.MatchString(ul) { 79 // Guess the dialect from the scheme 80 dialect := ul[:strings.Index(ul, ":")] 81 cd.Dialect = normalizeSynonyms(dialect) 82 } else { 83 return errors.New("no dialect provided, and could not guess it from URL") 84 } 85 } else if !dialectX.MatchString(ul) { 86 ul = cd.Dialect + "://" + ul 87 } 88 89 if !DialectSupported(cd.Dialect) { 90 return fmt.Errorf("unsupported dialect '%s'", cd.Dialect) 91 } 92 93 // warning message is required to prevent confusion 94 // even though this behavior was documented. 95 if cd.Database+cd.Host+cd.Port+cd.User+cd.Password != "" { 96 log(logging.Warn, "One or more of connection details are specified in database.yml. Override them with values in URL.") 97 } 98 99 if up, ok := urlParser[cd.Dialect]; ok { 100 return up(cd) 101 } 102 103 // Fallback on generic parsing if no URL parser was found for the dialect. 104 u, err := url.Parse(ul) 105 if err != nil { 106 return fmt.Errorf("couldn't parse %s: %w", ul, err) 107 } 108 cd.Database = strings.TrimPrefix(u.Path, "/") 109 110 hp := strings.Split(u.Host, ":") 111 cd.Host = hp[0] 112 if len(hp) > 1 { 113 cd.Port = hp[1] 114 } 115 116 if u.User != nil { 117 cd.User = u.User.Username() 118 cd.Password, _ = u.User.Password() 119 } 120 cd.RawOptions = u.RawQuery 121 122 return nil 123 } 124 125 // Finalize cleans up the connection details by normalizing names, 126 // filling in default values, etc... 127 func (cd *ConnectionDetails) Finalize() error { 128 cd.Dialect = normalizeSynonyms(cd.Dialect) 129 130 if cd.Options == nil { // for safety 131 cd.Options = make(map[string]string) 132 } 133 134 // Process the database connection string, if provided. 135 if cd.URL != "" { 136 if err := cd.withURL(); err != nil { 137 return err 138 } 139 } 140 141 if fin, ok := finalizer[cd.Dialect]; ok { 142 fin(cd) 143 } 144 145 if DialectSupported(cd.Dialect) { 146 if cd.Database != "" || cd.URL != "" { 147 return nil 148 } 149 return errors.New("no database or URL specified") 150 } 151 return fmt.Errorf("unsupported dialect '%v'", cd.Dialect) 152 } 153 154 // RetrySleep returns the amount of time to wait between two connection retries 155 func (cd *ConnectionDetails) RetrySleep() time.Duration { 156 d, err := time.ParseDuration(defaults.String(cd.Options["retry_sleep"], "1ms")) 157 if err != nil { 158 return 1 * time.Millisecond 159 } 160 return d 161 } 162 163 // RetryLimit returns the maximum number of accepted connection retries 164 func (cd *ConnectionDetails) RetryLimit() int { 165 i, err := strconv.Atoi(defaults.String(cd.Options["retry_limit"], "1000")) 166 if err != nil { 167 return 100 168 } 169 return i 170 } 171 172 // MigrationTableName returns the name of the table to track migrations 173 func (cd *ConnectionDetails) MigrationTableName() string { 174 return defaults.String(cd.Options["migration_table_name"], "schema_migration") 175 } 176 177 // OptionsString returns URL parameter encoded string from options. 178 func (cd *ConnectionDetails) OptionsString(s string) string { 179 if cd.RawOptions != "" { 180 return cd.RawOptions 181 } 182 if cd.Options != nil { 183 for k, v := range cd.Options { 184 if k == "migration_table_name" { 185 continue 186 } 187 188 s = fmt.Sprintf("%s&%s=%s", s, k, v) 189 } 190 } 191 return strings.TrimLeft(s, "&") 192 }