1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package mssql provides a Cloud SQL SQL Server driver that works with the 16 // database/sql package. 17 package mssql 18 19 import ( 20 "context" 21 "database/sql" 22 "database/sql/driver" 23 "net" 24 25 "cloud.google.com/go/cloudsqlconn" 26 mssqldb "github.com/microsoft/go-mssqldb" 27 "github.com/microsoft/go-mssqldb/msdsn" 28 ) 29 30 // RegisterDriver registers a SQL Server driver that uses the 31 // cloudsqlconn.Dialer configured with the provided options. The choice of name 32 // is entirely up to the caller and may be used to distinguish between multiple 33 // registrations of differently configured Dialers. 34 func RegisterDriver(name string, opts ...cloudsqlconn.Option) (func() error, error) { 35 d, err := cloudsqlconn.NewDialer(context.Background(), opts...) 36 if err != nil { 37 return func() error { return nil }, err 38 } 39 sql.Register(name, &sqlserverDriver{ 40 d: d, 41 }) 42 return func() error { return d.Close() }, nil 43 } 44 45 type csqlDialer struct { 46 driver.Conn 47 48 d *cloudsqlconn.Dialer 49 connName string 50 } 51 52 // DialContext adheres to the mssql.Dialer interface. 53 func (c *csqlDialer) DialContext(ctx context.Context, _, _ string) (net.Conn, error) { 54 return c.d.Dial(ctx, c.connName) 55 } 56 57 // Close ensures the cloudsqlconn.Dialer is closed before the conncetion is 58 // closed. 59 func (c *csqlDialer) Close() error { 60 c.d.Close() 61 return c.Conn.Close() 62 } 63 64 type sqlserverDriver struct { 65 d *cloudsqlconn.Dialer 66 } 67 68 // Open accepts a URL, ADO, or ODBC style connection string and returns a 69 // connection to the database using cloudsqlconn.Dialer. The Cloud SQL instance 70 // connection name should be specified in a "cloudsql" parameter. For example: 71 // 72 // "sqlserver://user:password@localhost?database=mydb&cloudsql=my-proj:us-central1:my-inst" 73 // 74 // For details, see 75 // https://github.com/microsoft/go-mssqldb#the-connection-string-can-be-specified-in-one-of-three-formats 76 func (s *sqlserverDriver) Open(name string) (driver.Conn, error) { 77 res, err := msdsn.Parse(name) 78 if err != nil { 79 return nil, err 80 } 81 c, err := mssqldb.NewConnector(name) 82 if err != nil { 83 return nil, err 84 } 85 connName := res.Parameters["cloudsql"] 86 c.Dialer = &csqlDialer{ 87 d: s.d, 88 connName: connName, 89 } 90 return c.Connect(context.Background()) 91 } 92