github.com/royge/pop@v4.13.1+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  }