diff --git a/README.md b/README.md index 60c533e8..012d0363 100644 --- a/README.md +++ b/README.md @@ -193,11 +193,23 @@ For further information on usage: * `server=localhost;user id=sa;database=master;app name=MyAppName;krb5-configfile=path/to/file;krb5-realm=domain.com;krb5-keytabfile=path/to/keytabfile;authenticator=krb5` - ADO strings support synonyms for database, app name, user id, and server + ADO strings support synonyms for common connection parameters: * server <= addr, address, network address, data source * user id <= user, uid + * password <= pwd * database <= initial catalog - * app name <= application name + * app name <= application name, app + * connection timeout <= connect timeout, timeout + * failoverpartner <= failover partner + * failoverpartnerspn <= failover partner spn + * applicationintent <= application intent + * trustservercertificate <= trust server certificate + * multisubnetfailover <= multi subnet failover + * hostnameincertificate <= host name in certificate + * serverspn <= server spn + * servercertificate <= server certificate + * workstation id <= wsid + * columnencryption <= column encryption setting 3. ODBC: Prefix with `odbc`, `key=value` pairs separated by `;`. Allow `;` by wrapping values in `{}`. Examples: diff --git a/msdsn/conn_str.go b/msdsn/conn_str.go index 9d5eb9e9..27adaffe 100644 --- a/msdsn/conn_str.go +++ b/msdsn/conn_str.go @@ -440,7 +440,7 @@ func Parse(dsn string) (Config, error) { } } - // https://docs.microsoft.com/en-us/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option\ + // https://docs.microsoft.com/sql/database-engine/configure-windows/configure-the-network-packet-size-server-configuration-option strpsize, ok := params[PacketSize] if ok { var err error @@ -462,7 +462,7 @@ func Parse(dsn string) (Config, error) { } } - // https://msdn.microsoft.com/en-us/library/dd341108.aspx + // https://msdn.microsoft.com/library/dd341108.aspx // // Do not set a connection timeout. Use Context to manage such things. // Default to zero, but still allow it to be set. @@ -476,7 +476,7 @@ func Parse(dsn string) (Config, error) { } // default keep alive should be 30 seconds according to spec: - // https://msdn.microsoft.com/en-us/library/dd341108.aspx + // https://msdn.microsoft.com/library/dd341108.aspx p.KeepAlive = 30 * time.Second if keepAlive, ok := params[KeepAlive]; ok { timeout, err := strconv.ParseUint(keepAlive, 10, 64) @@ -759,8 +759,10 @@ func (p Config) URL() *url.URL { return &res } -// ADO connection string keywords at https://github.com/dotnet/SqlClient/blob/main/src/Microsoft.Data.SqlClient/src/Microsoft/Data/Common/DbConnectionStringCommon.cs +// adoSynonyms maps ADO.Net alternate keyword forms to this driver's canonical keys. +// See https://learn.microsoft.com/dotnet/api/microsoft.data.sqlclient.sqlconnection.connectionstring var adoSynonyms = map[string]string{ + "app": AppName, "application name": AppName, "data source": Server, "address": Server, @@ -770,6 +772,17 @@ var adoSynonyms = map[string]string{ "uid": UserID, "pwd": Password, "initial catalog": Database, + "connect timeout": ConnectionTimeout, + "timeout": ConnectionTimeout, + "failover partner": FailoverPartner, + "failover partner spn": FailoverPartnerSpn, + "application intent": ApplicationIntent, + "trust server certificate": TrustServerCertificate, + "multi subnet failover": MultiSubnetFailover, + "host name in certificate": HostNameInCertificate, + "server spn": ServerSpn, + "server certificate": ServerCertificate, + "wsid": WorkstationID, "column encryption setting": "columnencryption", } diff --git a/msdsn/conn_str_test.go b/msdsn/conn_str_test.go index 08c9ea45..2eaa6412 100644 --- a/msdsn/conn_str_test.go +++ b/msdsn/conn_str_test.go @@ -186,6 +186,33 @@ func TestValidConnectionString(t *testing.T) { {"password=\"测试\"\"密码\"\"\"", func(p Config) bool { return p.Password == "测试\"密码\"" }}, // Chinese chars with escaped quotes {"password=\"café;naïve;résumé\"", func(p Config) bool { return p.Password == "café;naïve;résumé" }}, // Accented characters + // ADO.Net synonym tests + {"App=myapp", func(p Config) bool { return p.AppName == "myapp" }}, + {"Application Name=myapp", func(p Config) bool { return p.AppName == "myapp" }}, + {"Data Source=somehost", func(p Config) bool { return p.Host == "somehost" }}, + {"Address=somehost", func(p Config) bool { return p.Host == "somehost" }}, + {"Network Address=somehost", func(p Config) bool { return p.Host == "somehost" }}, + {"Addr=somehost", func(p Config) bool { return p.Host == "somehost" }}, + {"User=someuser", func(p Config) bool { return p.User == "someuser" }}, + {"UID=someuser", func(p Config) bool { return p.User == "someuser" }}, + {"PWD=somepass", func(p Config) bool { return p.Password == "somepass" }}, + {"Initial Catalog=mydb", func(p Config) bool { return p.Database == "mydb" }}, + {"Connect Timeout=60", func(p Config) bool { return p.ConnTimeout == 60*time.Second }}, + {"Timeout=45", func(p Config) bool { return p.ConnTimeout == 45*time.Second }}, + {"Failover Partner=mirror", func(p Config) bool { return p.FailOverPartner == "mirror" }}, + {"Failover Partner SPN=MSSQLSvc/mirror:1433", func(p Config) bool { return p.FailOverPartnerSPN == "MSSQLSvc/mirror:1433" }}, + {"Application Intent=ReadOnly;database=mydb", func(p Config) bool { return p.ReadOnlyIntent }}, + {"Trust Server Certificate=true;encrypt=true", func(p Config) bool { return p.TrustServerCertificate }}, + {"Multi Subnet Failover=false", func(p Config) bool { return !p.MultiSubnetFailover }}, + {"Host Name In Certificate=myhost", func(p Config) bool { return p.HostInCertificateProvided }}, + {"Server SPN=MSSQLSvc/myhost:1433", func(p Config) bool { return p.ServerSPN == "MSSQLSvc/myhost:1433" }}, + {"WSID=myworkstation", func(p Config) bool { return p.Workstation == "myworkstation" }}, + {"Column Encryption Setting=true", func(p Config) bool { return p.ColumnEncryption }}, + // Verify synonym keys work together in the same connection string + {"Data Source=somehost;Initial Catalog=mydb;Connect Timeout=30", func(p Config) bool { + return p.Host == "somehost" && p.Database == "mydb" && p.ConnTimeout == 30*time.Second + }}, + // those are supported currently, but maybe should not be {"someparam", func(p Config) bool { return true }}, {";;=;", func(p Config) bool { return true }}, @@ -302,6 +329,16 @@ func TestValidConnectionString(t *testing.T) { } } +func TestAdoSynonymServerCertificate(t *testing.T) { + // Server Certificate can't be tested through Parse() because parseTLS + // tries to read the cert file. Verify the synonym mapping at the + // splitConnectionString level instead. + params := splitConnectionString("Server Certificate=myfile.pem") + if v := params[ServerCertificate]; v != "myfile.pem" { + t.Fatalf("expected %s=myfile.pem, got %q", ServerCertificate, v) + } +} + func TestSplitConnectionStringURL(t *testing.T) { _, err := splitConnectionStringURL("http://bad") if err == nil {