Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
[Google Cloud Spanner](https://cloud.google.com/spanner) driver for
Go's [database/sql](https://golang.org/pkg/database/sql/) package.

This driver can be used with both Spanner GoogleSQL and Spanner PostgreSQL databases.

```go
import _ "github.com/googleapis/go-sql-spanner"

Expand All @@ -13,8 +15,8 @@ if err != nil {
log.Fatal(err)
}

// Print tweets with more than 500 likes.
rows, err := db.QueryContext(ctx, "SELECT id, text FROM tweets WHERE likes > @likes", sql.Named("likes", 500))
// Print singers with more than 500 likes.
rows, err := db.QueryContext(ctx, "SELECT id, name FROM singers WHERE likes > ?", 500)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -55,6 +57,17 @@ db.ExecContext(ctx, "INSERT INTO tweets (id, text, rts) VALUES (@id, @text, @rts
sql.Named("id", id), sql.Named("text", text), sql.Named("rts", 10000))
```

### Using PostgreSQL-style positional arguments

When connected to a PostgreSQL-dialect Spanner database, you can also use PostgreSQL-style
query parameters `$1, $2, ..., $n`. These query parameters are handled as positional parameters.

```go
db.QueryContext(ctx, "SELECT id, text FROM tweets WHERE likes > $1", 500)

db.ExecContext(ctx, "INSERT INTO tweets (id, text, rts) VALUES ($1, $2, $3)", id, text, 10000)
```

### Using named parameters with positional arguments (not recommended)
Named parameters can also be used in combination with positional arguments,
but this is __not recommended__, as the behavior can be hard to predict if
Expand Down Expand Up @@ -215,10 +228,19 @@ $ export SPANNER_EMULATOR_HOST=localhost:9010

## Spanner PostgreSQL Interface

This driver only works with the Spanner GoogleSQL dialect. For the
Spanner PostgreSQL dialect, any PostgreSQL driver that implements the
[database/sql](https://golang.org/pkg/database/sql/) interface can be used
in combination with
This driver works with both Spanner GoogleSQL and PostgreSQL dialects.
The driver automatically detects the dialect of the database that it is connected to.

Note that the types of query parameters that are supported depends on the dialect of the
database that the driver is connected to:
1. Positional parameters (`?`): Supported for both dialects.
2. Named parameters (`@id`, `@name`, ...): Supported for both dialects.
3. PostgreSQL-style positional parameters (`$1`, `$2`, ...): Only supported for PostgreSQL databases.

### Alternatives

You can also use Spanner PostgreSQL dialect databases with any off-the-shelf PostgreSQL driver that
implements the [database/sql](https://golang.org/pkg/database/sql/) interface in combination with
[PGAdapter](https://cloud.google.com/spanner/docs/pgadapter).

For example, the [pgx](https://github.com/jackc/pgx) driver can be used in combination with
Expand Down
17 changes: 14 additions & 3 deletions examples/emulator_runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ var containerId string
// 3. Execute the sample function against the emulator.
// 4. Stop the Docker container with the emulator.
func RunSampleOnEmulator(sample func(string, string, string) error, ddlStatements ...string) {
RunSampleOnEmulatorWithDialect(sample, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL, ddlStatements...)
}

func RunSampleOnEmulatorWithDialect(sample func(string, string, string) error, dialect databasepb.DatabaseDialect, ddlStatements ...string) {
var err error
if err = startEmulator(); err != nil {
log.Fatalf("failed to start emulator: %v", err)
Expand All @@ -53,7 +57,7 @@ func RunSampleOnEmulator(sample func(string, string, string) error, ddlStatement
stopEmulator()
log.Fatalf("failed to create instance on emulator: %v", err)
}
if err = createSampleDB(projectId, instanceId, databaseId, ddlStatements...); err != nil {
if err = createSampleDB(projectId, instanceId, databaseId, dialect, ddlStatements...); err != nil {
stopEmulator()
log.Fatalf("failed to create database on emulator: %v", err)
}
Expand Down Expand Up @@ -146,17 +150,24 @@ func createInstance(projectId, instanceId string) error {
return nil
}

func createSampleDB(projectId, instanceId, databaseId string, statements ...string) error {
func createSampleDB(projectId, instanceId, databaseId string, dialect databasepb.DatabaseDialect, statements ...string) error {
ctx := context.Background()
databaseAdminClient, err := database.NewDatabaseAdminClient(ctx)
if err != nil {
return err
}
defer databaseAdminClient.Close()
var createStatement string
if dialect == databasepb.DatabaseDialect_POSTGRESQL {
createStatement = fmt.Sprintf(`CREATE DATABASE "%s"`, databaseId)
} else {
createStatement = fmt.Sprintf("CREATE DATABASE `%s`", databaseId)
}
opDB, err := databaseAdminClient.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{
Parent: fmt.Sprintf("projects/%s/instances/%s", projectId, instanceId),
CreateStatement: fmt.Sprintf("CREATE DATABASE `%s`", databaseId),
CreateStatement: createStatement,
ExtraStatements: statements,
DatabaseDialect: dialect,
})
if err != nil {
return err
Expand Down
59 changes: 59 additions & 0 deletions examples/postgresql/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2025 Google LLC All Rights Reserved.
//
// 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 main

import (
"context"
"database/sql"
"fmt"

"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
_ "github.com/googleapis/go-sql-spanner"
"github.com/googleapis/go-sql-spanner/examples"
)

// Sample application that uses a PostgreSQL-dialect Spanner database.
//
// Execute the sample with the command `go run main.go` from this directory.
func helloWorldFromPostgreSQL(projectId, instanceId, databaseId string) error {
ctx := context.Background()
db, err := sql.Open("spanner", fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId))
if err != nil {
return fmt.Errorf("failed to open database connection: %v", err)
}
defer db.Close()

rows, err := db.QueryContext(ctx, "SELECT $1::varchar as message", "Hello World from Spanner PostgreSQL")
if err != nil {
return fmt.Errorf("failed to execute query: %v", err)
}
defer rows.Close()

var msg string
for rows.Next() {
if err := rows.Scan(&msg); err != nil {
return fmt.Errorf("failed to scan row values: %v", err)
}
fmt.Printf("%s\n", msg)
}
if err := rows.Err(); err != nil {
return fmt.Errorf("failed to execute query: %v", err)
}
return nil
}

func main() {
examples.RunSampleOnEmulatorWithDialect(helloWorldFromPostgreSQL, databasepb.DatabaseDialect_POSTGRESQL)
}
Loading