Skip to content

Commit

Permalink
feat(postgres/pgxv4): add support for postgres driver (#61)
Browse files Browse the repository at this point in the history
Related to #3.
  • Loading branch information
enocom committed Feb 4, 2022
1 parent f5f5e36 commit 295a5dc
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 21 deletions.
78 changes: 57 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,31 @@ the package's "Dial" option, which initializes a default dialer for you.

#### pgx for Postgres

Use the [pgConn.DialFunc field][pgconn-cfg] to create connections:
To use the dialer with [pgx](https://github.com/jackc/pgx), configure the
[pgConn.DialFunc field][pgconn-cfg] to create connections:

```go
// Configure the driver to connect to the database
dsn := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", pgUser, pgPass, pgDB)
config, err := pgx.ParseConfig(dsn)
if err != nil {
log.Fatalf("failed to parse pgx config: %v", err)
}

// Tell the driver to use the Cloud SQL Go Connector to create connections
config.DialFunc = func(ctx context.Context, network string, instance string) (net.Conn, error) {
return cloudsqlconn.Dial(ctx, "project:region:instance")
}
```go
// Configure the driver to connect to the database
dsn := fmt.Sprintf("user=%s password=%s dbname=%s sslmode=disable", pgUser, pgPass, pgDB)
config, err := pgx.ParseConfig(dsn)
if err != nil {
log.Fatalf("failed to parse pgx config: %v", err)
}

// Interact with the driver directly as you normally would
conn, connErr := pgx.ConnectConfig(ctx, config)
if connErr != nil {
log.Fatalf("failed to connect: %s", connErr)
}
defer conn.Close(ctx)
```
[pgconn-cfg]: https://pkg.go.dev/github.com/jackc/pgconn#Config
// Tell the driver to use the Cloud SQL Go Connector to create connections
config.DialFunc = func(ctx context.Context, network string, instance string) (net.Conn, error) {
return cloudsqlconn.Dial(ctx, "project:region:instance")
}

// Interact with the driver directly as you normally would
conn, connErr := pgx.ConnectConfig(ctx, config)
if connErr != nil {
log.Fatalf("failed to connect: %s", connErr)
}
defer conn.Close(ctx)
```

[pgconn-cfg]: https://pkg.go.dev/github.com/jackc/pgconn#Config

### Using Options

Expand Down Expand Up @@ -121,6 +121,42 @@ myDialer, err := cloudsqlconn.NewDialer(
)
```

### Using the dialer with database/sql

Using the dialer directly will expose more configuration options. However, it is
possible to use the dialer with the `database/sql` package.

#### postgres

To use `database/sql`, use `postgres.RegisterDriver` with any necessary Dialer
configuration. Note: the connection string must use the keyword/value format
with host set to the instance connection name.

``` go
package foo

import (
"database/sql"

"cloud.google.com/go/cloudsqlconn"
"cloud.google.com/go/cloudsqlconn/postgres/pgxv4"
)

func Connect() {
// Without any options:
pgxv4.RegisterDriver("cloudsql-postgres")

// Or, with options:
// pgxv4.RegisterDriver("cloudsql-postgres", cloudsqlconn.WithIAMAuthN())

db, err := sql.Open(
"cloudsql-postgres",
"host=project:region:instance user=myuser password=mypass dbname=mydb sslmode=disable"
)
// ... etc
}
```

### Enabling Metrics and Tracing

This library includes support for metrics and tracing using [OpenCensus][].
Expand Down
39 changes: 39 additions & 0 deletions e2e_postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package cloudsqlconn_test

import (
"context"
"database/sql"
"fmt"
"net"
"os"
Expand All @@ -29,6 +30,8 @@ import (

"cloud.google.com/go/cloudsqlconn"
"github.com/jackc/pgx/v4"

"cloud.google.com/go/cloudsqlconn/postgres/pgxv4"
)

var (
Expand Down Expand Up @@ -140,3 +143,39 @@ func TestEngineVersion(t *testing.T) {
t.Errorf("InstanceEngineVersion(%s) failed: want 'POSTGRES', got %v", gotEV, err)
}
}

func TestPostgresHook(t *testing.T) {
if testing.Short() {
t.Skip("skipping Postgres integration tests")
}
testConn := func(db *sql.DB) {
var now time.Time
if err := db.QueryRow("SELECT NOW()").Scan(&now); err != nil {
t.Fatalf("QueryRow failed: %v", err)
}
t.Log(now)
}
pgxv4.RegisterDriver("cloudsql-postgres")
db, err := sql.Open(
"cloudsql-postgres",
fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable",
postgresConnName, postgresUser, postgresPass, postgresDb),
)
if err != nil {
t.Fatalf("sql.Open want err = nil, got = %v", err)
}
defer db.Close()
testConn(db)

pgxv4.RegisterDriver("cloudsql-postgres-iam", cloudsqlconn.WithIAMAuthN())
db2, err := sql.Open(
"cloudsql-postgres-iam",
fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable",
postgresConnName, postgresUserIAM, postgresDb),
)
if err != nil {
t.Fatalf("sql.Open want err = nil, got = %v", err)
}
defer db2.Close()
testConn(db2)
}
79 changes: 79 additions & 0 deletions postgres/pgxv4/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package pgxv4 provides a Cloud SQL Postgres driver that uses pgx v4.
package pgxv4

import (
"context"
"database/sql"
"database/sql/driver"
"net"

"cloud.google.com/go/cloudsqlconn"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib"
)

// RegisterDriver registers a Postgres driver that uses the cloudsqlconn.Dialer
// configured with the provided options. The choice of name is entirely up to
// the caller and may be used to distinguish between multiple registrations of
// differently configured Dialers.
// Note: The underlying driver uses the latest version of pgx.
func RegisterDriver(name string, opts ...cloudsqlconn.Option) {
sql.Register(name, &pgDriver{
opts: opts,
})
}

type dialerConn struct {
driver.Conn
dialer *cloudsqlconn.Dialer
}

func (c *dialerConn) Close() error {
c.dialer.Close()
return c.Conn.Close()
}

type pgDriver struct {
opts []cloudsqlconn.Option
}

// Open accepts a keyword/value formatted connection string and returns a
// connection to the database using cloudsqlconn.Dialer. The Cloud SQL instance
// connection name should be specified in the host field. For example:
//
// "host=my-project:us-central1:my-db-instance user=myuser password=mypass"
func (p *pgDriver) Open(name string) (driver.Conn, error) {
config, err := pgx.ParseConfig(name)
if err != nil {
return nil, err
}
instConnName := config.Config.Host // Extract instance connection name
config.Config.Host = "localhost" // Replace it with a default value
d, err := cloudsqlconn.NewDialer(context.Background(), p.opts...)
if err != nil {
return nil, err
}
config.DialFunc = func(ctx context.Context, _, _ string) (net.Conn, error) {
return d.Dial(ctx, instConnName)
}
dbURI := stdlib.RegisterConnConfig(config)
conn, err := stdlib.GetDefaultDriver().Open(dbURI)
if err != nil {
return nil, err
}
return &dialerConn{Conn: conn, dialer: d}, nil
}
44 changes: 44 additions & 0 deletions postgres/pgxv4/postgres_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pgxv4_test

import (
"database/sql"
"log"
"time"

"cloud.google.com/go/cloudsqlconn"
"cloud.google.com/go/cloudsqlconn/postgres/pgxv4"
)

// Example shows how to use cloudsqlpostgres dialer
func ExamplePostgresConnection() {
// Note that sslmode=disable is required it does not mean that the connection
// is unencrypted. All connections via the proxy are completely encrypted.
pgxv4.RegisterDriver("cloudsql-postgres", cloudsqlconn.WithIAMAuthN())
db, err := sql.Open(
"cloudsql-postgres",
"host=project:region:instance user=postgres dbname=postgres password=password sslmode=disable",
)
if err != nil {
log.Fatal(err)
}
defer db.Close()
var now time.Time
if err := db.QueryRow("SELECT NOW()").Scan(&now); err != nil {
log.Fatal(err)
}
log.Println(now)
}

0 comments on commit 295a5dc

Please sign in to comment.