diff options
Diffstat (limited to 'vendor/github.com/go-pg/pg/v10/options.go')
-rw-r--r-- | vendor/github.com/go-pg/pg/v10/options.go | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/vendor/github.com/go-pg/pg/v10/options.go b/vendor/github.com/go-pg/pg/v10/options.go new file mode 100644 index 000000000..efd634fd2 --- /dev/null +++ b/vendor/github.com/go-pg/pg/v10/options.go @@ -0,0 +1,277 @@ +package pg + +import ( + "context" + "crypto/tls" + "errors" + "fmt" + "net" + "net/url" + "os" + "runtime" + "strconv" + "strings" + "time" + + "github.com/go-pg/pg/v10/internal/pool" +) + +// Options contains database connection options. +type Options struct { + // Network type, either tcp or unix. + // Default is tcp. + Network string + // TCP host:port or Unix socket depending on Network. + Addr string + + // Dialer creates new network connection and has priority over + // Network and Addr options. + Dialer func(ctx context.Context, network, addr string) (net.Conn, error) + + // Hook that is called after new connection is established + // and user is authenticated. + OnConnect func(ctx context.Context, cn *Conn) error + + User string + Password string + Database string + + // ApplicationName is the application name. Used in logs on Pg side. + // Only available from pg-9.0. + ApplicationName string + + // TLS config for secure connections. + TLSConfig *tls.Config + + // Dial timeout for establishing new connections. + // Default is 5 seconds. + DialTimeout time.Duration + + // Timeout for socket reads. If reached, commands will fail + // with a timeout instead of blocking. + ReadTimeout time.Duration + // Timeout for socket writes. If reached, commands will fail + // with a timeout instead of blocking. + WriteTimeout time.Duration + + // Maximum number of retries before giving up. + // Default is to not retry failed queries. + MaxRetries int + // Whether to retry queries cancelled because of statement_timeout. + RetryStatementTimeout bool + // Minimum backoff between each retry. + // Default is 250 milliseconds; -1 disables backoff. + MinRetryBackoff time.Duration + // Maximum backoff between each retry. + // Default is 4 seconds; -1 disables backoff. + MaxRetryBackoff time.Duration + + // Maximum number of socket connections. + // Default is 10 connections per every CPU as reported by runtime.NumCPU. + PoolSize int + // Minimum number of idle connections which is useful when establishing + // new connection is slow. + MinIdleConns int + // Connection age at which client retires (closes) the connection. + // It is useful with proxies like PgBouncer and HAProxy. + // Default is to not close aged connections. + MaxConnAge time.Duration + // Time for which client waits for free connection if all + // connections are busy before returning an error. + // Default is 30 seconds if ReadTimeOut is not defined, otherwise, + // ReadTimeout + 1 second. + PoolTimeout time.Duration + // Amount of time after which client closes idle connections. + // Should be less than server's timeout. + // Default is 5 minutes. -1 disables idle timeout check. + IdleTimeout time.Duration + // Frequency of idle checks made by idle connections reaper. + // Default is 1 minute. -1 disables idle connections reaper, + // but idle connections are still discarded by the client + // if IdleTimeout is set. + IdleCheckFrequency time.Duration +} + +func (opt *Options) init() { + if opt.Network == "" { + opt.Network = "tcp" + } + + if opt.Addr == "" { + switch opt.Network { + case "tcp": + host := env("PGHOST", "localhost") + port := env("PGPORT", "5432") + opt.Addr = fmt.Sprintf("%s:%s", host, port) + case "unix": + opt.Addr = "/var/run/postgresql/.s.PGSQL.5432" + } + } + + if opt.DialTimeout == 0 { + opt.DialTimeout = 5 * time.Second + } + if opt.Dialer == nil { + opt.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) { + netDialer := &net.Dialer{ + Timeout: opt.DialTimeout, + KeepAlive: 5 * time.Minute, + } + return netDialer.DialContext(ctx, network, addr) + } + } + + if opt.User == "" { + opt.User = env("PGUSER", "postgres") + } + + if opt.Database == "" { + opt.Database = env("PGDATABASE", "postgres") + } + + if opt.PoolSize == 0 { + opt.PoolSize = 10 * runtime.NumCPU() + } + + if opt.PoolTimeout == 0 { + if opt.ReadTimeout != 0 { + opt.PoolTimeout = opt.ReadTimeout + time.Second + } else { + opt.PoolTimeout = 30 * time.Second + } + } + + if opt.IdleTimeout == 0 { + opt.IdleTimeout = 5 * time.Minute + } + if opt.IdleCheckFrequency == 0 { + opt.IdleCheckFrequency = time.Minute + } + + switch opt.MinRetryBackoff { + case -1: + opt.MinRetryBackoff = 0 + case 0: + opt.MinRetryBackoff = 250 * time.Millisecond + } + switch opt.MaxRetryBackoff { + case -1: + opt.MaxRetryBackoff = 0 + case 0: + opt.MaxRetryBackoff = 4 * time.Second + } +} + +func env(key, defValue string) string { + envValue := os.Getenv(key) + if envValue != "" { + return envValue + } + return defValue +} + +// ParseURL parses an URL into options that can be used to connect to PostgreSQL. +func ParseURL(sURL string) (*Options, error) { + parsedURL, err := url.Parse(sURL) + if err != nil { + return nil, err + } + + // scheme + if parsedURL.Scheme != "postgres" && parsedURL.Scheme != "postgresql" { + return nil, errors.New("pg: invalid scheme: " + parsedURL.Scheme) + } + + // host and port + options := &Options{ + Addr: parsedURL.Host, + } + if !strings.Contains(options.Addr, ":") { + options.Addr += ":5432" + } + + // username and password + if parsedURL.User != nil { + options.User = parsedURL.User.Username() + + if password, ok := parsedURL.User.Password(); ok { + options.Password = password + } + } + + if options.User == "" { + options.User = "postgres" + } + + // database + if len(strings.Trim(parsedURL.Path, "/")) > 0 { + options.Database = parsedURL.Path[1:] + } else { + return nil, errors.New("pg: database name not provided") + } + + // ssl mode + query, err := url.ParseQuery(parsedURL.RawQuery) + if err != nil { + return nil, err + } + + if sslMode, ok := query["sslmode"]; ok && len(sslMode) > 0 { + switch sslMode[0] { + case "verify-ca", "verify-full": + options.TLSConfig = &tls.Config{} + case "allow", "prefer", "require": + options.TLSConfig = &tls.Config{InsecureSkipVerify: true} //nolint + case "disable": + options.TLSConfig = nil + default: + return nil, fmt.Errorf("pg: sslmode '%v' is not supported", sslMode[0]) + } + } else { + options.TLSConfig = &tls.Config{InsecureSkipVerify: true} //nolint + } + + delete(query, "sslmode") + + if appName, ok := query["application_name"]; ok && len(appName) > 0 { + options.ApplicationName = appName[0] + } + + delete(query, "application_name") + + if connTimeout, ok := query["connect_timeout"]; ok && len(connTimeout) > 0 { + ct, err := strconv.Atoi(connTimeout[0]) + if err != nil { + return nil, fmt.Errorf("pg: cannot parse connect_timeout option as int") + } + options.DialTimeout = time.Second * time.Duration(ct) + } + + delete(query, "connect_timeout") + + if len(query) > 0 { + return nil, errors.New("pg: options other than 'sslmode', 'application_name' and 'connect_timeout' are not supported") + } + + return options, nil +} + +func (opt *Options) getDialer() func(context.Context) (net.Conn, error) { + return func(ctx context.Context) (net.Conn, error) { + return opt.Dialer(ctx, opt.Network, opt.Addr) + } +} + +func newConnPool(opt *Options) *pool.ConnPool { + return pool.NewConnPool(&pool.Options{ + Dialer: opt.getDialer(), + OnClose: terminateConn, + + PoolSize: opt.PoolSize, + MinIdleConns: opt.MinIdleConns, + MaxConnAge: opt.MaxConnAge, + PoolTimeout: opt.PoolTimeout, + IdleTimeout: opt.IdleTimeout, + IdleCheckFrequency: opt.IdleCheckFrequency, + }) +} |