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  }