@@ -11,30 +11,50 @@ import (
1111 "net/url"
1212 "strings"
1313
14- // Provide access to the MySQL driver
15- _ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
14+ "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/go-sql-driver/mysql"
1615 gorp "github.com/letsencrypt/boulder/Godeps/_workspace/src/gopkg.in/gorp.v1"
1716 "github.com/letsencrypt/boulder/core"
1817 blog "github.com/letsencrypt/boulder/log"
1918)
2019
2120// NewDbMap creates the root gorp mapping object. Create one of these for each
22- // database schema you wish to map. Each DbMap contains a list of mapped tables.
23- // It automatically maps the tables for the primary parts of Boulder around the
24- // Storage Authority. This may require some further work when we use a disjoint
25- // schema, like that for `certificate-authority-data.go`.
21+ // database schema you wish to map. Each DbMap contains a list of mapped
22+ // tables. It automatically maps the tables for the primary parts of Boulder
23+ // around the Storage Authority.
2624func NewDbMap (dbConnect string ) (* gorp.DbMap , error ) {
27- logger := blog .GetAuditLogger ()
28-
2925 var err error
30- dbConnect , err = recombineURLForDB (dbConnect )
26+ var config * mysql.Config
27+ if strings .HasPrefix (dbConnect , "mysql+tcp://" ) {
28+ dbConnect , err = recombineCustomMySQLURL (dbConnect )
29+ if err != nil {
30+ return nil , err
31+ }
32+ }
33+
34+ config , err = mysql .ParseDSN (dbConnect )
3135 if err != nil {
3236 return nil , err
3337 }
3438
39+ return NewDbMapFromConfig (config )
40+ }
41+
42+ // sqlOpen is used in the tests to check that the arguments are properly
43+ // transformed
44+ var sqlOpen = func (dbType , connectStr string ) (* sql.DB , error ) {
45+ return sql .Open (dbType , connectStr )
46+ }
47+
48+ // NewDbMapFromConfig functions similarly to NewDbMap, but it takes the
49+ // decomposed form of the connection string, a *mysql.Config.
50+ func NewDbMapFromConfig (config * mysql.Config ) (* gorp.DbMap , error ) {
51+ adjustMySQLConfig (config )
52+
53+ logger := blog .GetAuditLogger ()
54+
3555 logger .Debug ("Connecting to database" )
3656
37- db , err := sql . Open ("mysql" , dbConnect )
57+ db , err := sqlOpen ("mysql" , config . FormatDSN () )
3858 if err != nil {
3959 return nil , err
4060 }
@@ -52,18 +72,45 @@ func NewDbMap(dbConnect string) (*gorp.DbMap, error) {
5272 return dbmap , err
5373}
5474
55- // recombineURLForDB transforms a database URL to a URL-like string
56- // that the mysql driver can use. The mysql driver needs the Host data
57- // to be wrapped in "tcp()" but url.Parse will escape the parentheses
58- // and the mysql driver doesn't understand them. So, we can't have
59- // "tcp()" in the configs, but can't leave it out before passing it to
60- // the mysql driver. Similarly, the driver needs the password and
61- // username unescaped. Compromise by doing the leg work if the config
62- // says the database URL's scheme is a fake one called
63- // "mysql+tcp://". See
64- // https://github.com/go-sql-driver/mysql/issues/362 for why we have
65- // to futz around and avoid URL.String.
66- func recombineURLForDB (dbConnect string ) (string , error ) {
75+ // adjustMySQLConfig sets certain flags that we want on every connection.
76+ func adjustMySQLConfig (conf * mysql.Config ) * mysql.Config {
77+ // Required to turn DATETIME fields into time.Time
78+ conf .ParseTime = true
79+
80+ // Required to make UPDATE return the number of rows matched,
81+ // instead of the number of rows changed by the UPDATE.
82+ conf .ClientFoundRows = true
83+
84+ // Ensures that MySQL/MariaDB warnings are treated as errors. This
85+ // avoids a number of nasty edge conditions we could wander into.
86+ // Common things this discovers includes places where data being sent
87+ // had a different type than what is in the schema, strings being
88+ // truncated, writing null to a NOT NULL column, and so on. See
89+ // <https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-strict>.
90+ conf .Strict = true
91+
92+ return conf
93+ }
94+
95+ // recombineCustomMySQLURL transforms the legacy database URLs into the
96+ // URL-like strings expected by the mysql database driver.
97+ //
98+ // In the past, changes to the connection string were achieved by passing it
99+ // into url.Parse and editing the query string that way, so the string had to
100+ // be a valid URL. The mysql driver needs the Host data to be wrapped in
101+ // "tcp()" but url.Parse will escape the parentheses and the mysql driver
102+ // doesn't understand them. So, we couldn't have "tcp()" in the configs, but
103+ // couldn't leave it out before passing it to the mysql driver. Similarly, the
104+ // driver needs the password and username unescaped. The compromise was to do
105+ // the leg work if the connection string's scheme is a fake one called
106+ // "mysql+tcp://".
107+ //
108+ // Upon the addition of
109+ // https://godoc.org/github.com/go-sql-driver/mysql#Config, this was no longer
110+ // necessary, as the changes could be made on the decomposed struct version of
111+ // the connection url. This method converts the old format into the format
112+ // expected by the library.
113+ func recombineCustomMySQLURL (dbConnect string ) (string , error ) {
67114 dbConnect = strings .TrimSpace (dbConnect )
68115 dbURL , err := url .Parse (dbConnect )
69116 if err != nil {
@@ -75,26 +122,6 @@ func recombineURLForDB(dbConnect string) (string, error) {
75122 return "" , fmt .Errorf (format , dbURL .Scheme )
76123 }
77124
78- dsnVals , err := url .ParseQuery (dbURL .RawQuery )
79- if err != nil {
80- return "" , err
81- }
82-
83- dsnVals .Set ("parseTime" , "true" )
84-
85- // Required to make UPDATE return the number of rows matched,
86- // instead of the number of rows changed by the UPDATE.
87- dsnVals .Set ("clientFoundRows" , "true" )
88-
89- // Ensures that MySQL/MariaDB warnings are treated as errors. This
90- // avoids a number of nasty edge conditions we could wander
91- // into. Common things this discovers includes places where data
92- // being sent had a different type than what is in the schema,
93- // strings being truncated, writing null to a NOT NULL column, and
94- // so on. See
95- // <https://dev.mysql.com/doc/refman/5.0/en/sql-mode.html#sql-mode-strict>.
96- dsnVals .Set ("strict" , "true" )
97-
98125 user := dbURL .User .Username ()
99126 passwd , hasPass := dbURL .User .Password ()
100127 dbConn := ""
@@ -105,7 +132,7 @@ func recombineURLForDB(dbConnect string) (string, error) {
105132 dbConn += ":" + passwd
106133 }
107134 dbConn += "@tcp(" + dbURL .Host + ")"
108- return dbConn + dbURL .EscapedPath () + "?" + dsnVals . Encode () , nil
135+ return dbConn + dbURL .EscapedPath () + "?" + dbURL . RawQuery , nil
109136}
110137
111138// SetSQLDebug enables/disables GORP SQL-level Debugging
0 commit comments