Skip to content

Commit 30df107

Browse files
authored
feat: support PostgreSQL-dialect databases (#443)
The driver now supports both GoogleSQL and PostgreSQL databases. The driver automatically detects the dialect of the database that it is connected to, and adjusts the query parameter styles and comments that it accepts accordingly.
1 parent 40888f3 commit 30df107

File tree

3 files changed

+101
-9
lines changed

3 files changed

+101
-9
lines changed

README.md

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
[Google Cloud Spanner](https://cloud.google.com/spanner) driver for
66
Go's [database/sql](https://golang.org/pkg/database/sql/) package.
77

8+
This driver can be used with both Spanner GoogleSQL and Spanner PostgreSQL databases.
9+
810
```go
911
import _ "github.com/googleapis/go-sql-spanner"
1012

@@ -13,8 +15,8 @@ if err != nil {
1315
log.Fatal(err)
1416
}
1517

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

60+
### Using PostgreSQL-style positional arguments
61+
62+
When connected to a PostgreSQL-dialect Spanner database, you can also use PostgreSQL-style
63+
query parameters `$1, $2, ..., $n`. These query parameters are handled as positional parameters.
64+
65+
```go
66+
db.QueryContext(ctx, "SELECT id, text FROM tweets WHERE likes > $1", 500)
67+
68+
db.ExecContext(ctx, "INSERT INTO tweets (id, text, rts) VALUES ($1, $2, $3)", id, text, 10000)
69+
```
70+
5871
### Using named parameters with positional arguments (not recommended)
5972
Named parameters can also be used in combination with positional arguments,
6073
but this is __not recommended__, as the behavior can be hard to predict if
@@ -215,10 +228,19 @@ $ export SPANNER_EMULATOR_HOST=localhost:9010
215228

216229
## Spanner PostgreSQL Interface
217230

218-
This driver only works with the Spanner GoogleSQL dialect. For the
219-
Spanner PostgreSQL dialect, any PostgreSQL driver that implements the
220-
[database/sql](https://golang.org/pkg/database/sql/) interface can be used
221-
in combination with
231+
This driver works with both Spanner GoogleSQL and PostgreSQL dialects.
232+
The driver automatically detects the dialect of the database that it is connected to.
233+
234+
Note that the types of query parameters that are supported depends on the dialect of the
235+
database that the driver is connected to:
236+
1. Positional parameters (`?`): Supported for both dialects.
237+
2. Named parameters (`@id`, `@name`, ...): Supported for both dialects.
238+
3. PostgreSQL-style positional parameters (`$1`, `$2`, ...): Only supported for PostgreSQL databases.
239+
240+
### Alternatives
241+
242+
You can also use Spanner PostgreSQL dialect databases with any off-the-shelf PostgreSQL driver that
243+
implements the [database/sql](https://golang.org/pkg/database/sql/) interface in combination with
222244
[PGAdapter](https://cloud.google.com/spanner/docs/pgadapter).
223245

224246
For example, the [pgx](https://github.com/jackc/pgx) driver can be used in combination with

examples/emulator_runner.go

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ var containerId string
4444
// 3. Execute the sample function against the emulator.
4545
// 4. Stop the Docker container with the emulator.
4646
func RunSampleOnEmulator(sample func(string, string, string) error, ddlStatements ...string) {
47+
RunSampleOnEmulatorWithDialect(sample, databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL, ddlStatements...)
48+
}
49+
50+
func RunSampleOnEmulatorWithDialect(sample func(string, string, string) error, dialect databasepb.DatabaseDialect, ddlStatements ...string) {
4751
var err error
4852
if err = startEmulator(); err != nil {
4953
log.Fatalf("failed to start emulator: %v", err)
@@ -53,7 +57,7 @@ func RunSampleOnEmulator(sample func(string, string, string) error, ddlStatement
5357
stopEmulator()
5458
log.Fatalf("failed to create instance on emulator: %v", err)
5559
}
56-
if err = createSampleDB(projectId, instanceId, databaseId, ddlStatements...); err != nil {
60+
if err = createSampleDB(projectId, instanceId, databaseId, dialect, ddlStatements...); err != nil {
5761
stopEmulator()
5862
log.Fatalf("failed to create database on emulator: %v", err)
5963
}
@@ -146,17 +150,24 @@ func createInstance(projectId, instanceId string) error {
146150
return nil
147151
}
148152

149-
func createSampleDB(projectId, instanceId, databaseId string, statements ...string) error {
153+
func createSampleDB(projectId, instanceId, databaseId string, dialect databasepb.DatabaseDialect, statements ...string) error {
150154
ctx := context.Background()
151155
databaseAdminClient, err := database.NewDatabaseAdminClient(ctx)
152156
if err != nil {
153157
return err
154158
}
155159
defer databaseAdminClient.Close()
160+
var createStatement string
161+
if dialect == databasepb.DatabaseDialect_POSTGRESQL {
162+
createStatement = fmt.Sprintf(`CREATE DATABASE "%s"`, databaseId)
163+
} else {
164+
createStatement = fmt.Sprintf("CREATE DATABASE `%s`", databaseId)
165+
}
156166
opDB, err := databaseAdminClient.CreateDatabase(ctx, &databasepb.CreateDatabaseRequest{
157167
Parent: fmt.Sprintf("projects/%s/instances/%s", projectId, instanceId),
158-
CreateStatement: fmt.Sprintf("CREATE DATABASE `%s`", databaseId),
168+
CreateStatement: createStatement,
159169
ExtraStatements: statements,
170+
DatabaseDialect: dialect,
160171
})
161172
if err != nil {
162173
return err

examples/postgresql/main.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright 2025 Google LLC All Rights Reserved.
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 main
16+
17+
import (
18+
"context"
19+
"database/sql"
20+
"fmt"
21+
22+
"cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
23+
_ "github.com/googleapis/go-sql-spanner"
24+
"github.com/googleapis/go-sql-spanner/examples"
25+
)
26+
27+
// Sample application that uses a PostgreSQL-dialect Spanner database.
28+
//
29+
// Execute the sample with the command `go run main.go` from this directory.
30+
func helloWorldFromPostgreSQL(projectId, instanceId, databaseId string) error {
31+
ctx := context.Background()
32+
db, err := sql.Open("spanner", fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId))
33+
if err != nil {
34+
return fmt.Errorf("failed to open database connection: %v", err)
35+
}
36+
defer db.Close()
37+
38+
rows, err := db.QueryContext(ctx, "SELECT $1::varchar as message", "Hello World from Spanner PostgreSQL")
39+
if err != nil {
40+
return fmt.Errorf("failed to execute query: %v", err)
41+
}
42+
defer rows.Close()
43+
44+
var msg string
45+
for rows.Next() {
46+
if err := rows.Scan(&msg); err != nil {
47+
return fmt.Errorf("failed to scan row values: %v", err)
48+
}
49+
fmt.Printf("%s\n", msg)
50+
}
51+
if err := rows.Err(); err != nil {
52+
return fmt.Errorf("failed to execute query: %v", err)
53+
}
54+
return nil
55+
}
56+
57+
func main() {
58+
examples.RunSampleOnEmulatorWithDialect(helloWorldFromPostgreSQL, databasepb.DatabaseDialect_POSTGRESQL)
59+
}

0 commit comments

Comments
 (0)