Skip to content

Commit

Permalink
Fixing issues with unix domain socket paths for MySQL/PostgreSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
Kenneth Shaw committed Mar 24, 2017
1 parent 06741ad commit 38dcf1f
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 44 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ Where:
| Component | Description |
|--------------------|--------------------------------------------------------------------------------------|
| protocol | driver name or alias (see below) |
| user | the username to connect as |
| pass | the password to use |
| host | the remote host |
| dbname<sup>*</sup> | the database, instance, or service name/ID to connect to |
| transport | "tcp", "udp", "unix" or driver name (odbc/oleodbc) |
| user | username |
| pass | password |
| host | host |
| dbname<sup>*</sup> | database, instance, or service name/ID to connect to |
| ?opt1=... | additional database driver options (see respective SQL driver for available options) |

<i><sup><b>*</b></sup> for Microsoft SQL Server, the syntax to supply an
Expand All @@ -40,8 +41,8 @@ u, err := dburl.Parse("postgresql://user:pass@localhost/mydatabase/?sslmode=disa
if err != nil { /* ... */ }
```

Additionally, a simple helper func `Open`, is available to simply parse,
open, and return the SQL database connection:
Additionally, a simple helper func `Open`, is available to quickly parse, open,
and return a standard SQL database connection:

```go
db, err := dburl.Open("sqlite:mydatabase.sqlite3?loc=auto")
Expand Down
17 changes: 10 additions & 7 deletions dburl.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
// Where:
//
// protocol - driver name or alias (see below)
// transport - the transport protocol [tcp, udp, unix] (only mysql for now)
// user - the username to connect as
// pass - the password to use
// host - the remote host
// dbname* - the database, instance, or service name/id to connect to
// transport - "tcp", "udp", "unix" or driver name (odbc/oleodbc) |
// user - username
// pass - password
// host - host
// dbname* - database, instance, or service name/id to connect to
// ?opt1=... - additional database driver options
// (see respective SQL driver for available options)
//
Expand All @@ -30,8 +30,8 @@
// u, err := dburl.Parse("postgresql://user:pass@localhost/mydatabase/?sslmode=disable")
// if err != nil { /* ... */ }
//
// Additionally, a simple helper func, Open, is available to simply parse,
// open, and return the SQL database connection:
// Additionally, a simple helper func, Open, is available to quickly parse,
// open, and return a standard SQL database connection:
//
// db, err := dburl.Open("sqlite:mydatabase.sqlite3?loc=auto")
// if err != nil { /* ... */ }
Expand Down Expand Up @@ -169,6 +169,9 @@ var (

// ErrInvalidHost is the invalid host error.
ErrInvalidHost = errors.New("invalid host")

// ErrPostgresDoesNotSupportRelativePath is the postgres does not support relative path error.
ErrPostgresDoesNotSupportRelativePath = errors.New("postgres does not support relative path")
)

// Open takes a urlstr like "protocol+transport://user:pass@host/dbname?option1=a&option2=b"
Expand Down
31 changes: 26 additions & 5 deletions dburl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ func TestBadParse(t *testing.T) {
{``},
{`pgsqlx://`},
{`m`},
{`pg+udp://`},
{`pg+udp://user:pass@localhost/dbname`},
{`sqlite+unix://`},
{`sqlite+tcp://`},
{`file+tcp://`},
Expand All @@ -18,6 +18,16 @@ func TestBadParse(t *testing.T) {
{`mssql+unix:/var/run/mssql.sock`},
{`mssql+udp:localhost:155`},
{`adodb+foo+bar://provider/database`},
{`memsql:/var/run/mysqld/mysqld.sock`},
{`tidb:/var/run/mysqld/mysqld.sock`},
{`vitess:/var/run/mysqld/mysqld.sock`},
{`memsql+unix:///var/run/mysqld/mysqld.sock`},
{`tidb+unix:///var/run/mysqld/mysqld.sock`},
{`vitess+unix:///var/run/mysqld/mysqld.sock`},
{`cockroach:/var/run/postgresql`},
{`cockroach+unix:/var/run/postgresql`},
{`pg:./path/to/socket`}, // relative paths are not possible for postgres sockets
{`pg+unix:./path/to/socket`},
}

for i, test := range tests {
Expand Down Expand Up @@ -51,24 +61,35 @@ func TestParse(t *testing.T) {
{`my:/var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mysql`, `unix(/var/run/mysqld/mysqld.sock)/mydb?timeout=90`},
{`my:///var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mysql`, `unix(/var/run/mysqld/mysqld.sock)/mydb?timeout=90`},
{`my+unix:user:pass@mysqld.sock?timeout=90`, `mysql`, `user:pass@unix(mysqld.sock)/?timeout=90`},
{`my:./path/to/socket`, `mysql`, `unix(path/to/socket)/`},
{`my+unix:./path/to/socket`, `mysql`, `unix(path/to/socket)/`},

{`mymy:`, `mymysql`, `tcp:127.0.0.1:3306*`}, // 16
{`mymy:`, `mymysql`, `tcp:127.0.0.1:3306*`}, // 18
{`mymy://`, `mymysql`, `tcp:127.0.0.1:3306*`},
{`mymy:user:pass@localhost/booktest`, `mymysql`, `tcp:localhost:3306*booktest/user/pass`},
{`mymy:/var/run/mysqld/mysqld.sock/mydb?timeout=90&test=true`, `mymysql`, `unix:/var/run/mysqld/mysqld.sock,test,timeout=90*mydb`},
{`mymy:///var/run/mysqld/mysqld.sock/mydb?timeout=90`, `mymysql`, `unix:/var/run/mysqld/mysqld.sock,timeout=90*mydb`},
{`mymy+unix:user:pass@mysqld.sock?timeout=90`, `mymysql`, `unix:mysqld.sock,timeout=90*/user/pass`},
{`mymy:./path/to/socket`, `mymysql`, `unix:path/to/socket*`},
{`mymy+unix:./path/to/socket`, `mymysql`, `unix:path/to/socket*`},

{`mssql://`, `mssql`, ``}, // 22
{`mssql://`, `mssql`, ``}, // 26
{`mssql://user:pass@localhost/dbname`, `mssql`, `Database=dbname;Password=pass;Server=localhost;User ID=user`},
{`mssql://user@localhost/service/dbname`, `mssql`, `Database=dbname;Server=localhost\service;User ID=user`},
{`mssql://user:!234%23$@localhost:1580/dbname`, `mssql`, `Database=dbname;Password=!234#$;Port=1580;Server=localhost;User ID=user`},

{`adodb://Microsoft.ACE.OLEDB.12.0?Extended+Properties=%22Text%3BHDR%3DNO%3BFMT%3DDelimited%22`, `adodb`,
`Data Source=.;Extended Properties="Text;HDR=NO;FMT=Delimited";Provider=Microsoft.ACE.OLEDB.12.0`}, // 26
`Data Source=.;Extended Properties="Text;HDR=NO;FMT=Delimited";Provider=Microsoft.ACE.OLEDB.12.0`}, // 30
{`adodb://user:pass@Provider.Name:1542/dbname`, `adodb`, `Database=dbname;Password=pass;Port=1542;Provider=Provider.Name;User ID=user`},

{`oo+Postgres+Unicode://user:pass@host:5432/dbname`, `adodb`,
`Provider=MSDASQL.1;Extended Properties="Database=dbname;Driver={Postgres Unicode};PWD=pass;Port=5432;Server=host;UID=user"`}, // 27
`Provider=MSDASQL.1;Extended Properties="Database=dbname;Driver={Postgres Unicode};PWD=pass;Port=5432;Server=host;UID=user"`}, // 31

{`file:/path/to/file.sqlite3`, `sqlite3`, `/path/to/file.sqlite3`}, // 33
{`sqlite:///path/to/file.sqlite3`, `sqlite3`, `/path/to/file.sqlite3`},
{`sq://path/to/file.sqlite3`, `sqlite3`, `path/to/file.sqlite3`},
{`sq:path/to/file.sqlite3`, `sqlite3`, `path/to/file.sqlite3`},
{`sq:./path/to/file.sqlite3`, `sqlite3`, `./path/to/file.sqlite3`},
}

for i, test := range tests {
Expand Down
41 changes: 25 additions & 16 deletions dsn.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ func GenPostgres(u *URL) (string, error) {
q := u.Query()

host, port, dbname := u.Hostname(), u.Port(), strings.TrimPrefix(u.Path, "/")
if host == "." {
return "", ErrPostgresDoesNotSupportRelativePath
}

// resolve path
if u.Proto == "unix" {
Expand All @@ -114,12 +117,9 @@ func GenPostgres(u *URL) (string, error) {

// add user/pass
if u.User != nil {
if user := u.User.Username(); user != "" {
q.Set("user", user)
if pass, ok := u.User.Password(); ok {
q.Set("password", pass)
}
}
q.Set("user", u.User.Username())
pass, _ := u.User.Password()
q.Set("password", pass)
}

return genOptions(q, "", "=", " ", ",", true), nil
Expand Down Expand Up @@ -298,15 +298,11 @@ func GenOracle(u *URL) (string, error) {
return un + "@" + dsn, nil
}

// GenSQLite3 generates a sqlite3 DSN from the passed URL.
func GenSQLite3(u *URL) (string, error) {
// GenOpaque generates a opaque file path DSN from the passed URL.
func GenOpaque(u *URL) (string, error) {
dsn := u.Opaque
if u.Path != "" {
dsn = u.Path
}

if u.Host != "" && u.Host != "localhost" {
dsn = stdpath.Join(u.Host, dsn)
if u.Host != "" {
dsn = u.Host + u.Path
}

// add params
Expand Down Expand Up @@ -335,14 +331,27 @@ func GenFirebird(u *URL) (string, error) {
// GenADODB generates a adodb DSN from the passed URL.
func GenADODB(u *URL) (string, error) {
q := u.Query()
q.Set("Provider", u.Host)
q.Set("Provider", u.Hostname())
q.Set("Port", u.Port())

// grab dbname
dbname := strings.TrimPrefix(u.Path, "/")
if dbname == "" {
dbname = "."
}
q.Set("Data Source", dbname)

if dbname == "." {
q.Set("Data Source", dbname)
} else {
q.Set("Database", dbname)
}

// add user/pass
if u.User != nil {
q.Set("User ID", u.User.Username())
pass, _ := u.User.Password()
q.Set("Password", pass)
}

return genOptionsODBC(q, true), nil
}
Expand Down
12 changes: 8 additions & 4 deletions scheme.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func BaseSchemes() []Scheme {
{"mysql", GenMySQL, ProtoTCP | ProtoUDP | ProtoUnix, false, []string{"mariadb", "maria", "percona", "aurora"}, ""},
{"ora", GenOracle, 0, false, []string{"oracle", "oci8", "oci"}, ""},
{"postgres", GenPostgres, ProtoUnix, false, []string{"pg", "postgresql", "pgsql"}, ""},
{"sqlite3", GenSQLite3, 0, true, []string{"sqlite", "file"}, ""},
{"sqlite3", GenOpaque, 0, true, []string{"sqlite", "file"}, ""},

// wire compatibles
{"cockroachdb", GenFromURL("postgres://localhost:26257/?sslmode=disable"), 0, false, []string{"cr", "cockroach", "crdb", "cdb"}, "postgres"},
Expand All @@ -78,9 +78,9 @@ func BaseSchemes() []Scheme {
{"firebirdsql", GenFirebird, 0, false, []string{"fb", "firebird"}, ""},
{"hdb", GenScheme("hdb"), 0, false, []string{"sa", "saphana", "sap", "hana"}, ""},
{"n1ql", GenFromURL("http://localhost:9000/"), 0, false, []string{"couchbase"}, ""},
{"odbc", GenODBC, ProtoAny, true, nil, ""},
{"oleodbc", GenOLEODBC, ProtoAny, true, []string{"oo", "ole"}, "adodb"},
{"ql", GenSQLite3, 0, true, []string{"ql", "cznic", "cznicql"}, ""},
{"odbc", GenODBC, ProtoAny, false, nil, ""},
{"oleodbc", GenOLEODBC, ProtoAny, false, []string{"oo", "ole"}, "adodb"},
{"ql", GenOpaque, 0, true, []string{"ql", "cznic", "cznicql"}, ""},
{"sqlany", GenSybase, 0, false, []string{"sy", "sybase", "any"}, ""},
{"yql", GenYQL, 0, false, nil, ""},
{"voltdb", GenVoltDB, 0, false, []string{"volt", "vdb"}, ""},
Expand Down Expand Up @@ -129,6 +129,10 @@ func Register(scheme Scheme) {
panic("must specify Generator when registering Scheme")
}

if scheme.Opaque && scheme.Proto&ProtoUnix != 0 {
panic("scheme must support only Opaque or Unix protocols, not both")
}

// register
if _, ok := schemeMap[scheme.Driver]; ok {
panic(fmt.Sprintf("scheme %s already registered", scheme.Driver))
Expand Down
15 changes: 9 additions & 6 deletions url.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,16 @@ func Parse(urlstr string) (*URL, error) {
return Parse(v.OriginalScheme + "://" + v.Opaque + q + f)
}

if scheme.Opaque && v.Opaque == "" {
// force Opaque
v.Opaque, v.Host, v.Path, v.RawPath = v.Host+v.Path, "", "", ""
} else if v.Host == "." || (v.Host == "" && strings.TrimPrefix(v.Path, "/") != "") {
// force unix proto
v.Proto = "unix"
}

// check proto
if checkProto {
if checkProto || v.Proto != "tcp" {
if scheme.Proto == ProtoNone {
return nil, ErrInvalidTransportProtocol
}
Expand All @@ -92,11 +100,6 @@ func Parse(urlstr string) (*URL, error) {
}
}

// force unix proto
if host, dbname := v.Host, strings.TrimPrefix(v.Path, "/"); !scheme.Opaque && scheme.Proto&ProtoUnix != 0 && host == "" && dbname != "" {
v.Proto = "unix"
}

// set driver
v.Driver = scheme.Driver
if scheme.Override != "" {
Expand Down

0 comments on commit 38dcf1f

Please sign in to comment.