Skip to content

Commit da4e3c8

Browse files
authoredFeb 13, 2025··
docs: add samples for getting started guide (#361)
* docs: add samples for getting started guide * build: add snippets test workflow * chore: go mod tidy * docs: add DataBoost example * fix: remove hello world sample * chore: go mod tidy * docs: add snippet runner * test: fix tests * chore: minor cleanups * chore: go mod tidy
1 parent 1a9b3b9 commit da4e3c8

23 files changed

+3286
-0
lines changed
 

‎.github/workflows/snippets.yml

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
on:
2+
pull_request:
3+
branches: [ main ]
4+
name: Run Snippets
5+
jobs:
6+
samples:
7+
strategy:
8+
matrix:
9+
go-version: [1.22.x, 1.23.x]
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Install Go
13+
uses: actions/setup-go@v5
14+
with:
15+
go-version: ${{ matrix.go-version }}
16+
- name: Checkout code
17+
uses: actions/checkout@v4
18+
- name: Run snippets
19+
working-directory: ./snippets
20+
run: go test -short

‎.github/workflows/unit-tests.yml

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ jobs:
4343
working-directory: ./examples
4444
run: go vet ./...
4545

46+
- name: vet ./snippets
47+
working-directory: ./snippets
48+
run: go vet ./...
49+
4650
- name: vet ./benchmarks
4751
working-directory: ./benchmarks
4852
run: go vet ./...

‎snippets/getting_started_guide.go

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Copyright 2025 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+
// https://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+
"flag"
20+
"fmt"
21+
"io"
22+
"os"
23+
"time"
24+
25+
"github.com/googleapis/go-sql-spanner/examples/samples"
26+
)
27+
28+
type command func(ctx context.Context, w io.Writer, databaseName string) error
29+
30+
var (
31+
commands = map[string]command{
32+
"createconnection": samples.CreateConnection,
33+
"createtables": samples.CreateTables,
34+
"dmlwrite": samples.WriteDataWithDml,
35+
"write": samples.WriteDataWithMutations,
36+
"query": samples.QueryData,
37+
"querywithparameter": samples.QueryDataWithParameter,
38+
"addcolumn": samples.AddColumn,
39+
"ddlbatch": samples.DdlBatch,
40+
"update": samples.UpdateDataWithMutations,
41+
"querymarketingbudget": samples.QueryNewColumn,
42+
"writewithtransactionusingdml": samples.WriteWithTransactionUsingDml,
43+
"tags": samples.Tags,
44+
"readonlytransaction": samples.ReadOnlyTransaction,
45+
"databoost": samples.DataBoost,
46+
"pdml": samples.PartitionedDml,
47+
}
48+
)
49+
50+
func run(ctx context.Context, w io.Writer, cmd string, db string) error {
51+
cmdFn := commands[cmd]
52+
if cmdFn == nil {
53+
flag.Usage()
54+
os.Exit(2)
55+
}
56+
err := cmdFn(ctx, w, db)
57+
if err != nil {
58+
fmt.Fprintf(w, "%s failed with %v", cmd, err)
59+
}
60+
return err
61+
}
62+
63+
func main() {
64+
flag.Usage = func() {
65+
fmt.Fprintf(os.Stderr, `Usage: getting_started_guide <command> <database_name>
66+
Examples:
67+
spanner_snippets write projects/my-project/instances/my-instance/databases/example-db
68+
`)
69+
}
70+
71+
flag.Parse()
72+
if len(flag.Args()) < 2 || len(flag.Args()) > 3 {
73+
flag.Usage()
74+
os.Exit(2)
75+
}
76+
77+
cmd, db := flag.Arg(0), flag.Arg(1)
78+
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
79+
defer cancel()
80+
if err := run(ctx, os.Stdout, cmd, db); err != nil {
81+
os.Exit(1)
82+
}
83+
84+
}
+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Copyright 2025 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+
// https://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+
"bytes"
19+
"context"
20+
"fmt"
21+
"io"
22+
"os"
23+
"testing"
24+
25+
database "cloud.google.com/go/spanner/admin/database/apiv1"
26+
adminpb "cloud.google.com/go/spanner/admin/database/apiv1/databasepb"
27+
instance "cloud.google.com/go/spanner/admin/instance/apiv1"
28+
"cloud.google.com/go/spanner/admin/instance/apiv1/instancepb"
29+
"github.com/docker/docker/api/types/container"
30+
"github.com/googleapis/go-sql-spanner/examples/samples"
31+
"github.com/testcontainers/testcontainers-go"
32+
"github.com/testcontainers/testcontainers-go/wait"
33+
)
34+
35+
func TestSamples(t *testing.T) {
36+
projectID := "emulator-project"
37+
instanceID := "test-instance"
38+
databaseID := "test-database"
39+
databaseName := fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectID, instanceID, databaseID)
40+
41+
emulator, err := startEmulator(projectID, instanceID, databaseID)
42+
if err != nil {
43+
if emulator != nil {
44+
emulator.Terminate(context.Background())
45+
}
46+
t.Fatalf("failed to start emulator: %v", err)
47+
48+
}
49+
defer emulator.Terminate(context.Background())
50+
51+
ctx := context.Background()
52+
var b bytes.Buffer
53+
54+
testSample(t, ctx, &b, databaseName, samples.CreateTables, "CreateTables", fmt.Sprintf("Created Singers & Albums tables in database: [%s]\n", databaseName))
55+
testSample(t, ctx, &b, databaseName, samples.CreateConnection, "CreateConnection", "Greeting from Spanner: Hello world!\n")
56+
testSample(t, ctx, &b, databaseName, samples.WriteDataWithDml, "WriteDataWithDml", "4 records inserted\n")
57+
testSample(t, ctx, &b, databaseName, samples.WriteDataWithDmlBatch, "WriteDataWithDmlBatch", "3 records inserted\n")
58+
testSample(t, ctx, &b, databaseName, samples.WriteDataWithMutations, "WriteDataWithMutations", "Inserted 10 rows\n")
59+
testSample(t, ctx, &b, databaseName, samples.QueryData, "QueryData", "1 1 Total Junk\n1 2 Go, Go, Go\n2 1 Green\n2 2 Forever Hold Your Peace\n2 3 Terrified\n")
60+
testSample(t, ctx, &b, databaseName, samples.QueryDataWithParameter, "QueryDataWithParameter", "12 Melissa Garcia\n")
61+
testSample(t, ctx, &b, databaseName, samples.QueryDataWithTimeout, "QueryDataWithTimeout", "")
62+
testSample(t, ctx, &b, databaseName, samples.AddColumn, "AddColumn", "Added MarketingBudget column\n")
63+
testSample(t, ctx, &b, databaseName, samples.DdlBatch, "DdlBatch", "Added Venues and Concerts tables\n")
64+
testSample(t, ctx, &b, databaseName, samples.UpdateDataWithMutations, "UpdateDataWithMutations", "Updated 2 albums\n")
65+
testSample(t, ctx, &b, databaseName, samples.QueryNewColumn, "QueryNewColumn", "1 1 100000\n1 2 NULL\n2 1 NULL\n2 2 500000\n2 3 NULL\n")
66+
testSample(t, ctx, &b, databaseName, samples.WriteWithTransactionUsingDml, "WriteWithTransactionUsingDml", "Transferred marketing budget from Album 2 to Album 1\n")
67+
testSample(t, ctx, &b, databaseName, samples.Tags, "Tags", "Reduced marketing budget\n")
68+
testSample(t, ctx, &b, databaseName, samples.ReadOnlyTransaction, "ReadOnlyTransaction", "1 1 Total Junk\n1 2 Go, Go, Go\n2 1 Green\n2 2 Forever Hold Your Peace\n2 3 Terrified\n2 2 Forever Hold Your Peace\n1 2 Go, Go, Go\n2 1 Green\n2 3 Terrified\n1 1 Total Junk\n")
69+
testSample(t, ctx, &b, databaseName, samples.DataBoost, "DataBoost", "1 Marc Richards\n2 Catalina Smith\n3 Alice Trentor\n4 Lea Martin\n5 David Lomond\n12 Melissa Garcia\n13 Russel Morales\n14 Jacqueline Long\n15 Dylan Shaw\n16 Sarah Wilson\n17 Ethan Miller\n18 Maya Patel\n")
70+
testSample(t, ctx, &b, databaseName, samples.PartitionedDml, "PDML", "Updated at least 3 albums\n")
71+
}
72+
73+
func testSample(t *testing.T, ctx context.Context, b *bytes.Buffer, databaseName string, sample func(ctx context.Context, w io.Writer, databaseName string) error, sampleName, want string) {
74+
if err := sample(ctx, b, databaseName); err != nil {
75+
t.Fatalf("failed to run %s: %v", sampleName, err)
76+
}
77+
if g, w := b.String(), want; g != w {
78+
t.Fatalf("%s output mismatch\n Got: %v\nWant: %v", sampleName, g, w)
79+
}
80+
b.Reset()
81+
}
82+
83+
func startEmulator(projectID, instanceID, databaseID string) (testcontainers.Container, error) {
84+
ctx := context.Background()
85+
req := testcontainers.ContainerRequest{
86+
AlwaysPullImage: true,
87+
Image: "gcr.io/cloud-spanner-emulator/emulator",
88+
ExposedPorts: []string{"9010/tcp"},
89+
WaitingFor: wait.ForListeningPort("9010/tcp"),
90+
HostConfigModifier: func(hostConfig *container.HostConfig) {
91+
hostConfig.AutoRemove = true
92+
},
93+
}
94+
emulator, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
95+
ContainerRequest: req,
96+
Started: true,
97+
})
98+
if err != nil {
99+
return emulator, fmt.Errorf("failed to start PGAdapter: %v", err)
100+
}
101+
host, err := emulator.Host(ctx)
102+
if err != nil {
103+
return emulator, fmt.Errorf("failed to get host: %v", err)
104+
}
105+
mappedPort, err := emulator.MappedPort(ctx, "9010/tcp")
106+
if err != nil {
107+
return emulator, fmt.Errorf("failed to get mapped port: %v", err)
108+
}
109+
port := mappedPort.Int()
110+
// Set the env var to connec to the emulator.
111+
if err := os.Setenv("SPANNER_EMULATOR_HOST", fmt.Sprintf("%s:%v", host, port)); err != nil {
112+
return emulator, fmt.Errorf("failed to set env var for emulator: %v", err)
113+
}
114+
if err := createInstance(projectID, instanceID); err != nil {
115+
return emulator, fmt.Errorf("failed to create instance: %v", err)
116+
}
117+
if err := createDatabase(projectID, instanceID, databaseID); err != nil {
118+
return emulator, fmt.Errorf("failed to create database: %v", err)
119+
}
120+
return emulator, nil
121+
}
122+
123+
func createInstance(projectID, instanceID string) error {
124+
ctx := context.Background()
125+
instanceAdmin, err := instance.NewInstanceAdminClient(ctx)
126+
if err != nil {
127+
return err
128+
}
129+
defer instanceAdmin.Close()
130+
131+
op, err := instanceAdmin.CreateInstance(ctx, &instancepb.CreateInstanceRequest{
132+
Parent: fmt.Sprintf("projects/%s", projectID),
133+
InstanceId: instanceID,
134+
Instance: &instancepb.Instance{
135+
Config: fmt.Sprintf("projects/%s/instanceConfigs/%s", projectID, "regional-us-central1"),
136+
DisplayName: instanceID,
137+
NodeCount: 1,
138+
Labels: map[string]string{"cloud_spanner_samples": "true"},
139+
Edition: instancepb.Instance_STANDARD,
140+
},
141+
})
142+
if err != nil {
143+
return fmt.Errorf("could not create instance %s: %w", fmt.Sprintf("projects/%s/instances/%s", projectID, instanceID), err)
144+
}
145+
// Wait for the instance creation to finish.
146+
if _, err = op.Wait(ctx); err != nil {
147+
return fmt.Errorf("waiting for instance creation to finish failed: %w", err)
148+
}
149+
return nil
150+
}
151+
152+
func createDatabase(projectID, instanceID, databaseID string) error {
153+
ctx := context.Background()
154+
adminClient, err := database.NewDatabaseAdminClient(ctx)
155+
if err != nil {
156+
return err
157+
}
158+
defer adminClient.Close()
159+
160+
op, err := adminClient.CreateDatabase(ctx, &adminpb.CreateDatabaseRequest{
161+
Parent: fmt.Sprintf("projects/%s/instances/%s", projectID, instanceID),
162+
CreateStatement: fmt.Sprintf("CREATE DATABASE `%s`", databaseID),
163+
})
164+
if err != nil {
165+
return err
166+
}
167+
if _, err := op.Wait(ctx); err != nil {
168+
return err
169+
}
170+
return nil
171+
}

‎snippets/go.mod

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
module github.com/googleapis/go-sql-spanner/examples
2+
3+
go 1.22.7
4+
5+
toolchain go1.22.9
6+
7+
replace github.com/googleapis/go-sql-spanner => ../
8+
9+
require (
10+
cloud.google.com/go/spanner v1.75.0
11+
github.com/docker/docker v27.1.1+incompatible
12+
github.com/googleapis/go-sql-spanner v0.0.0-00010101000000-000000000000
13+
github.com/testcontainers/testcontainers-go v0.35.0
14+
)
15+
16+
require (
17+
cel.dev/expr v0.19.0 // indirect
18+
cloud.google.com/go v0.118.2 // indirect
19+
cloud.google.com/go/auth v0.14.1 // indirect
20+
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
21+
cloud.google.com/go/compute/metadata v0.6.0 // indirect
22+
cloud.google.com/go/iam v1.3.1 // indirect
23+
cloud.google.com/go/longrunning v0.6.4 // indirect
24+
cloud.google.com/go/monitoring v1.23.0 // indirect
25+
dario.cat/mergo v1.0.0 // indirect
26+
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
27+
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect
28+
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
29+
github.com/Microsoft/go-winio v0.6.2 // indirect
30+
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
31+
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
32+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
33+
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
34+
github.com/containerd/containerd v1.7.18 // indirect
35+
github.com/containerd/log v0.1.0 // indirect
36+
github.com/containerd/platforms v0.2.1 // indirect
37+
github.com/cpuguy83/dockercfg v0.3.2 // indirect
38+
github.com/davecgh/go-spew v1.1.1 // indirect
39+
github.com/distribution/reference v0.6.0 // indirect
40+
github.com/docker/go-connections v0.5.0 // indirect
41+
github.com/docker/go-units v0.5.0 // indirect
42+
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
43+
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
44+
github.com/felixge/httpsnoop v1.0.4 // indirect
45+
github.com/go-logr/logr v1.4.2 // indirect
46+
github.com/go-logr/stdr v1.2.2 // indirect
47+
github.com/go-ole/go-ole v1.2.6 // indirect
48+
github.com/gogo/protobuf v1.3.2 // indirect
49+
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
50+
github.com/google/s2a-go v0.1.9 // indirect
51+
github.com/google/uuid v1.6.0 // indirect
52+
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
53+
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
54+
github.com/klauspost/compress v1.17.4 // indirect
55+
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
56+
github.com/magiconair/properties v1.8.7 // indirect
57+
github.com/moby/docker-image-spec v1.3.1 // indirect
58+
github.com/moby/patternmatcher v0.6.0 // indirect
59+
github.com/moby/sys/sequential v0.5.0 // indirect
60+
github.com/moby/sys/user v0.1.0 // indirect
61+
github.com/moby/term v0.5.0 // indirect
62+
github.com/morikuni/aec v1.0.0 // indirect
63+
github.com/opencontainers/go-digest v1.0.0 // indirect
64+
github.com/opencontainers/image-spec v1.1.0 // indirect
65+
github.com/pkg/errors v0.9.1 // indirect
66+
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
67+
github.com/pmezard/go-difflib v1.0.0 // indirect
68+
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
69+
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
70+
github.com/shoenig/go-m1cpu v0.1.6 // indirect
71+
github.com/sirupsen/logrus v1.9.3 // indirect
72+
github.com/stretchr/testify v1.10.0 // indirect
73+
github.com/tklauser/go-sysconf v0.3.12 // indirect
74+
github.com/tklauser/numcpus v0.6.1 // indirect
75+
github.com/yusufpapurcu/wmi v1.2.3 // indirect
76+
go.opencensus.io v0.24.0 // indirect
77+
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
78+
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect
79+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
80+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
81+
go.opentelemetry.io/otel v1.34.0 // indirect
82+
go.opentelemetry.io/otel/metric v1.34.0 // indirect
83+
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
84+
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
85+
go.opentelemetry.io/otel/trace v1.34.0 // indirect
86+
golang.org/x/crypto v0.32.0 // indirect
87+
golang.org/x/net v0.34.0 // indirect
88+
golang.org/x/oauth2 v0.25.0 // indirect
89+
golang.org/x/sync v0.10.0 // indirect
90+
golang.org/x/sys v0.29.0 // indirect
91+
golang.org/x/text v0.21.0 // indirect
92+
golang.org/x/time v0.9.0 // indirect
93+
google.golang.org/api v0.220.0 // indirect
94+
google.golang.org/genproto v0.0.0-20250122153221-138b5a5a4fd4 // indirect
95+
google.golang.org/genproto/googleapis/api v0.0.0-20250124145028-65684f501c47 // indirect
96+
google.golang.org/genproto/googleapis/rpc v0.0.0-20250207221924-e9438ea467c6 // indirect
97+
google.golang.org/grpc v1.70.0 // indirect
98+
google.golang.org/protobuf v1.36.5 // indirect
99+
gopkg.in/yaml.v3 v3.0.1 // indirect
100+
)

‎snippets/go.sum

+1,725
Large diffs are not rendered by default.

‎snippets/samples/add_column.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_add_column]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func AddColumn(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
_, err = db.ExecContext(ctx,
35+
`ALTER TABLE Albums
36+
ADD COLUMN MarketingBudget INT64`)
37+
if err != nil {
38+
return err
39+
}
40+
41+
fmt.Fprint(w, "Added MarketingBudget column\n")
42+
return nil
43+
}
44+
45+
// [END spanner_add_column]

‎snippets/samples/create_connection.go

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_create_connection]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func CreateConnection(ctx context.Context, w io.Writer, databaseName string) error {
28+
// The dataSourceName should start with a fully qualified Spanner database name
29+
// in the format `projects/my-project/instances/my-instance/databases/my-database`.
30+
// Additional properties can be added after the database name by
31+
// adding one or more `;name=value` pairs.
32+
33+
dsn := fmt.Sprintf("%s;numChannels=8", databaseName)
34+
db, err := sql.Open("spanner", dsn)
35+
if err != nil {
36+
return err
37+
}
38+
defer db.Close()
39+
40+
row := db.QueryRowContext(ctx, "select 'Hello world!' as hello")
41+
var msg string
42+
if err := row.Scan(&msg); err != nil {
43+
return err
44+
}
45+
fmt.Fprintf(w, "Greeting from Spanner: %s\n", msg)
46+
return nil
47+
}
48+
49+
// [END spanner_create_connection]

‎snippets/samples/create_tables.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_create_database]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func CreateTables(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Create two tables in one batch on Spanner.
35+
conn, err := db.Conn(ctx)
36+
defer conn.Close()
37+
38+
// Start a DDL batch on the connection.
39+
// This instructs the connection to buffer all DDL statements until the
40+
// command `run batch` is executed.
41+
if _, err := conn.ExecContext(ctx, "start batch ddl"); err != nil {
42+
return err
43+
}
44+
if _, err := conn.ExecContext(ctx,
45+
`CREATE TABLE Singers (
46+
SingerId INT64 NOT NULL,
47+
FirstName STRING(1024),
48+
LastName STRING(1024),
49+
SingerInfo BYTES(MAX)
50+
) PRIMARY KEY (SingerId)`); err != nil {
51+
return err
52+
}
53+
if _, err := conn.ExecContext(ctx,
54+
`CREATE TABLE Albums (
55+
SingerId INT64 NOT NULL,
56+
AlbumId INT64 NOT NULL,
57+
AlbumTitle STRING(MAX)
58+
) PRIMARY KEY (SingerId, AlbumId),
59+
INTERLEAVE IN PARENT Singers ON DELETE CASCADE`); err != nil {
60+
return err
61+
}
62+
// `run batch` sends the DDL statements to Spanner and blocks until
63+
// all statements have finished executing.
64+
if _, err := conn.ExecContext(ctx, "run batch"); err != nil {
65+
return err
66+
}
67+
68+
fmt.Fprintf(w, "Created Singers & Albums tables in database: [%s]\n", databaseName)
69+
return nil
70+
}
71+
72+
// [END spanner_create_database]

‎snippets/samples/data_boost.go

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_data_boost]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
"slices"
24+
25+
"cloud.google.com/go/spanner"
26+
spannerdriver "github.com/googleapis/go-sql-spanner"
27+
)
28+
29+
func DataBoost(ctx context.Context, w io.Writer, databaseName string) error {
30+
db, err := sql.Open("spanner", databaseName)
31+
if err != nil {
32+
return err
33+
}
34+
defer db.Close()
35+
36+
// Run a partitioned query that uses Data Boost.
37+
rows, err := db.QueryContext(ctx,
38+
"SELECT SingerId, FirstName, LastName from Singers",
39+
spannerdriver.ExecOptions{
40+
PartitionedQueryOptions: spannerdriver.PartitionedQueryOptions{
41+
// AutoPartitionQuery instructs the Spanner database/sql driver to
42+
// automatically partition the query and execute each partition in parallel.
43+
// The rows are returned as one result set in undefined order.
44+
AutoPartitionQuery: true,
45+
},
46+
QueryOptions: spanner.QueryOptions{
47+
// Set DataBoostEnabled to true to enable DataBoost.
48+
// See https://cloud.google.com/spanner/docs/databoost/databoost-overview
49+
// for more information.
50+
DataBoostEnabled: true,
51+
},
52+
})
53+
defer rows.Close()
54+
if err != nil {
55+
return err
56+
}
57+
type Singer struct {
58+
SingerId int64
59+
FirstName string
60+
LastName string
61+
}
62+
var singers []Singer
63+
for rows.Next() {
64+
var singer Singer
65+
err = rows.Scan(&singer.SingerId, &singer.FirstName, &singer.LastName)
66+
if err != nil {
67+
return err
68+
}
69+
singers = append(singers, singer)
70+
}
71+
// Queries that use the AutoPartition option return rows in undefined order,
72+
// so we need to sort them in memory to guarantee the output order.
73+
slices.SortFunc(singers, func(a, b Singer) int {
74+
return int(a.SingerId - b.SingerId)
75+
})
76+
for _, s := range singers {
77+
fmt.Fprintf(w, "%v %v %v\n", s.SingerId, s.FirstName, s.LastName)
78+
}
79+
80+
return nil
81+
}
82+
83+
// [END spanner_data_boost]

‎snippets/samples/ddl_batch.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_ddl_batch]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func DdlBatch(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Executing multiple DDL statements as one batch is
35+
// more efficient than executing each statement
36+
// individually.
37+
conn, err := db.Conn(ctx)
38+
defer conn.Close()
39+
40+
if _, err := conn.ExecContext(ctx, "start batch ddl"); err != nil {
41+
return err
42+
}
43+
if _, err := conn.ExecContext(ctx,
44+
`CREATE TABLE Venues (
45+
VenueId INT64 NOT NULL,
46+
Name STRING(1024),
47+
Description JSON,
48+
) PRIMARY KEY (VenueId)`); err != nil {
49+
return err
50+
}
51+
if _, err := conn.ExecContext(ctx,
52+
`CREATE TABLE Concerts (
53+
ConcertId INT64 NOT NULL,
54+
VenueId INT64 NOT NULL,
55+
SingerId INT64 NOT NULL,
56+
StartTime TIMESTAMP,
57+
EndTime TIMESTAMP,
58+
CONSTRAINT Fk_Concerts_Venues FOREIGN KEY
59+
(VenueId) REFERENCES Venues (VenueId),
60+
CONSTRAINT Fk_Concerts_Singers FOREIGN KEY
61+
(SingerId) REFERENCES Singers (SingerId),
62+
) PRIMARY KEY (ConcertId)`); err != nil {
63+
return err
64+
}
65+
// `run batch` sends the DDL statements to Spanner and blocks until
66+
// all statements have finished executing.
67+
if _, err := conn.ExecContext(ctx, "run batch"); err != nil {
68+
return err
69+
}
70+
71+
fmt.Fprint(w, "Added Venues and Concerts tables\n")
72+
return nil
73+
}
74+
75+
// [END spanner_ddl_batch]

‎snippets/samples/pdml.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_partitioned_dml]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func PartitionedDml(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
conn, err := db.Conn(ctx)
35+
if err != nil {
36+
return err
37+
}
38+
// Enable Partitioned DML on this connection.
39+
if _, err := conn.ExecContext(ctx, "SET AUTOCOMMIT_DML_MODE='PARTITIONED_NON_ATOMIC'"); err != nil {
40+
return fmt.Errorf("failed to change DML mode to Partitioned_Non_Atomic: %v", err)
41+
}
42+
// Back-fill a default value for the MarketingBudget column.
43+
res, err := conn.ExecContext(ctx, "UPDATE Albums SET MarketingBudget=0 WHERE MarketingBudget IS NULL")
44+
if err != nil {
45+
return err
46+
}
47+
affected, err := res.RowsAffected()
48+
if err != nil {
49+
return fmt.Errorf("failed to get affected rows: %v", err)
50+
}
51+
52+
// Partitioned DML returns the minimum number of records that were affected.
53+
fmt.Fprintf(w, "Updated at least %v albums\n", affected)
54+
55+
// Closing the connection will return it to the connection pool. The DML mode will automatically be reset to the
56+
// default TRANSACTIONAL mode when the connection is returned to the pool, so we do not need to change it back
57+
// manually.
58+
_ = conn.Close()
59+
60+
return nil
61+
}
62+
63+
// [END spanner_partitioned_dml]

‎snippets/samples/query_data.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_query_data]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func QueryData(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
rows, err := db.QueryContext(ctx,
35+
`SELECT SingerId, AlbumId, AlbumTitle
36+
FROM Albums
37+
ORDER BY SingerId, AlbumId`)
38+
defer rows.Close()
39+
if err != nil {
40+
return err
41+
}
42+
for rows.Next() {
43+
var singerId, albumId int64
44+
var title string
45+
err = rows.Scan(&singerId, &albumId, &title)
46+
if err != nil {
47+
return err
48+
}
49+
fmt.Fprintf(w, "%v %v %v\n", singerId, albumId, title)
50+
}
51+
if rows.Err() != nil {
52+
return rows.Err()
53+
}
54+
return rows.Close()
55+
}
56+
57+
// [END spanner_query_data]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_query_data_with_new_column]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func QueryNewColumn(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
rows, err := db.QueryContext(ctx,
35+
`SELECT SingerId, AlbumId, MarketingBudget
36+
FROM Albums
37+
ORDER BY SingerId, AlbumId`)
38+
defer rows.Close()
39+
if err != nil {
40+
return err
41+
}
42+
for rows.Next() {
43+
var singerId, albumId int64
44+
var marketingBudget sql.NullInt64
45+
err = rows.Scan(&singerId, &albumId, &marketingBudget)
46+
if err != nil {
47+
return err
48+
}
49+
budget := "NULL"
50+
if marketingBudget.Valid {
51+
budget = fmt.Sprintf("%v", marketingBudget.Int64)
52+
}
53+
fmt.Fprintf(w, "%v %v %v\n", singerId, albumId, budget)
54+
}
55+
if rows.Err() != nil {
56+
return rows.Err()
57+
}
58+
return rows.Close()
59+
}
60+
61+
// [END spanner_query_data_with_new_column]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_query_with_parameter]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func QueryDataWithParameter(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
rows, err := db.QueryContext(ctx,
35+
`SELECT SingerId, FirstName, LastName
36+
FROM Singers
37+
WHERE LastName = ?`, "Garcia")
38+
defer rows.Close()
39+
if err != nil {
40+
return err
41+
}
42+
for rows.Next() {
43+
var singerId int64
44+
var firstName, lastName string
45+
err = rows.Scan(&singerId, &firstName, &lastName)
46+
if err != nil {
47+
return err
48+
}
49+
fmt.Fprintf(w, "%v %v %v\n", singerId, firstName, lastName)
50+
}
51+
if rows.Err() != nil {
52+
return rows.Err()
53+
}
54+
return rows.Close()
55+
}
56+
57+
// [END spanner_query_with_parameter]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_statement_timeout]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
"time"
24+
25+
_ "github.com/googleapis/go-sql-spanner"
26+
)
27+
28+
func QueryDataWithTimeout(ctx context.Context, w io.Writer, databaseName string) error {
29+
db, err := sql.Open("spanner", databaseName)
30+
if err != nil {
31+
return err
32+
}
33+
defer db.Close()
34+
35+
// Use QueryContext and pass in a context with a timeout to execute
36+
// a query with a timeout.
37+
ctx, cancel := context.WithTimeout(ctx, time.Second*5)
38+
defer cancel()
39+
rows, err := db.QueryContext(ctx,
40+
`SELECT SingerId, AlbumId, AlbumTitle
41+
FROM Albums
42+
WHERE AlbumTitle in (
43+
SELECT FirstName
44+
FROM Singers
45+
WHERE LastName LIKE '%a%'
46+
OR LastName LIKE '%m%'
47+
)`)
48+
defer rows.Close()
49+
if err != nil {
50+
return err
51+
}
52+
for rows.Next() {
53+
var singerId, albumId int64
54+
var title string
55+
err = rows.Scan(&singerId, &albumId, &title)
56+
if err != nil {
57+
return err
58+
}
59+
fmt.Fprintf(w, "%v %v %v\n", singerId, albumId, title)
60+
}
61+
if rows.Err() != nil {
62+
return rows.Err()
63+
}
64+
return rows.Close()
65+
}
66+
67+
// [END spanner_statement_timeout]
+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_read_only_transaction]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func ReadOnlyTransaction(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Start a read-only transaction by supplying additional transaction options.
35+
tx, err := db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true})
36+
37+
albumsOrderedById, err := tx.QueryContext(ctx,
38+
`SELECT SingerId, AlbumId, AlbumTitle
39+
FROM Albums
40+
ORDER BY SingerId, AlbumId`)
41+
defer albumsOrderedById.Close()
42+
if err != nil {
43+
return err
44+
}
45+
for albumsOrderedById.Next() {
46+
var singerId, albumId int64
47+
var title string
48+
err = albumsOrderedById.Scan(&singerId, &albumId, &title)
49+
if err != nil {
50+
return err
51+
}
52+
fmt.Fprintf(w, "%v %v %v\n", singerId, albumId, title)
53+
}
54+
55+
albumsOrderedTitle, err := tx.QueryContext(ctx,
56+
`SELECT SingerId, AlbumId, AlbumTitle
57+
FROM Albums
58+
ORDER BY AlbumTitle`)
59+
defer albumsOrderedTitle.Close()
60+
if err != nil {
61+
return err
62+
}
63+
for albumsOrderedTitle.Next() {
64+
var singerId, albumId int64
65+
var title string
66+
err = albumsOrderedTitle.Scan(&singerId, &albumId, &title)
67+
if err != nil {
68+
return err
69+
}
70+
fmt.Fprintf(w, "%v %v %v\n", singerId, albumId, title)
71+
}
72+
73+
// End the read-only transaction by calling Commit.
74+
return tx.Commit()
75+
}
76+
77+
// [END spanner_read_only_transaction]

‎snippets/samples/tags.go

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_transaction_and_statement_tag]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
"cloud.google.com/go/spanner"
25+
spannerdriver "github.com/googleapis/go-sql-spanner"
26+
)
27+
28+
func Tags(ctx context.Context, w io.Writer, databaseName string) error {
29+
db, err := sql.Open("spanner", databaseName)
30+
if err != nil {
31+
return err
32+
}
33+
defer db.Close()
34+
35+
// Use the spannerdriver.BeginReadWriteTransaction function
36+
// to specify specific Spanner options, such as transaction tags.
37+
tx, err := spannerdriver.BeginReadWriteTransaction(ctx, db,
38+
spannerdriver.ReadWriteTransactionOptions{
39+
TransactionOptions: spanner.TransactionOptions{
40+
TransactionTag: "example-tx-tag",
41+
},
42+
})
43+
if err != nil {
44+
return err
45+
}
46+
47+
// Pass in an argument of type spannerdriver.ExecOptions to supply
48+
// additional options for a statement.
49+
row := tx.QueryRowContext(ctx, "SELECT MarketingBudget "+
50+
"FROM Albums "+
51+
"WHERE SingerId=? and AlbumId=?",
52+
spannerdriver.ExecOptions{
53+
QueryOptions: spanner.QueryOptions{RequestTag: "query-marketing-budget"},
54+
}, 1, 1)
55+
var budget int64
56+
if err := row.Scan(&budget); err != nil {
57+
tx.Rollback()
58+
return err
59+
}
60+
61+
// Reduce the marketing budget by 10% if it is more than 1,000.
62+
if budget > 1000 {
63+
budget = int64(float64(budget) - float64(budget)*0.1)
64+
if _, err := tx.ExecContext(ctx,
65+
`UPDATE Albums SET MarketingBudget=@budget
66+
WHERE SingerId=@singerId AND AlbumId=@albumId`,
67+
spannerdriver.ExecOptions{
68+
QueryOptions: spanner.QueryOptions{RequestTag: "reduce-marketing-budget"},
69+
},
70+
sql.Named("budget", budget),
71+
sql.Named("singerId", 1),
72+
sql.Named("albumId", 1)); err != nil {
73+
tx.Rollback()
74+
return err
75+
}
76+
}
77+
// Commit the current transaction.
78+
if err := tx.Commit(); err != nil {
79+
return err
80+
}
81+
fmt.Fprintln(w, "Reduced marketing budget")
82+
83+
return nil
84+
}
85+
86+
// [END spanner_transaction_and_statement_tag]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_update_data]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
"cloud.google.com/go/spanner"
25+
spannerdriver "github.com/googleapis/go-sql-spanner"
26+
)
27+
28+
func UpdateDataWithMutations(ctx context.Context, w io.Writer, databaseName string) error {
29+
db, err := sql.Open("spanner", databaseName)
30+
if err != nil {
31+
return err
32+
}
33+
defer db.Close()
34+
35+
// Get a connection so that we can get access to the Spanner specific
36+
// connection interface SpannerConn.
37+
conn, err := db.Conn(ctx)
38+
if err != nil {
39+
return err
40+
}
41+
defer conn.Close()
42+
43+
cols := []string{"SingerId", "AlbumId", "MarketingBudget"}
44+
mutations := []*spanner.Mutation{
45+
spanner.Update("Albums", cols, []interface{}{1, 1, 100000}),
46+
spanner.Update("Albums", cols, []interface{}{2, 2, 500000}),
47+
}
48+
if err := conn.Raw(func(driverConn interface{}) error {
49+
spannerConn, ok := driverConn.(spannerdriver.SpannerConn)
50+
if !ok {
51+
return fmt.Errorf("unexpected driver connection %v, "+
52+
"expected SpannerConn", driverConn)
53+
}
54+
_, err = spannerConn.Apply(ctx, mutations)
55+
return err
56+
}); err != nil {
57+
return err
58+
}
59+
fmt.Fprintf(w, "Updated %v albums\n", len(mutations))
60+
61+
return nil
62+
}
63+
64+
// [END spanner_update_data]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_dml_getting_started_update]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func WriteWithTransactionUsingDml(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Transfer marketing budget from one album to another. We do it in a
35+
// transaction to ensure that the transfer is atomic.
36+
tx, err := db.BeginTx(ctx, &sql.TxOptions{})
37+
if err != nil {
38+
return err
39+
}
40+
// The Spanner database/sql driver supports both positional and named
41+
// query parameters. This query uses named query parameters.
42+
const selectSql = "SELECT MarketingBudget " +
43+
"FROM Albums " +
44+
"WHERE SingerId = @singerId and AlbumId = @albumId"
45+
// Get the marketing_budget of singer 2 / album 2.
46+
row := tx.QueryRowContext(ctx, selectSql,
47+
sql.Named("singerId", 2), sql.Named("albumId", 2))
48+
var budget2 int64
49+
if err := row.Scan(&budget2); err != nil {
50+
tx.Rollback()
51+
return err
52+
}
53+
const transfer = 20000
54+
// The transaction will only be committed if this condition still holds
55+
// at the time of commit. Otherwise, the transaction will be aborted.
56+
if budget2 >= transfer {
57+
// Get the marketing_budget of singer 1 / album 1.
58+
row := tx.QueryRowContext(ctx, selectSql,
59+
sql.Named("singerId", 1), sql.Named("albumId", 1))
60+
var budget1 int64
61+
if err := row.Scan(&budget1); err != nil {
62+
tx.Rollback()
63+
return err
64+
}
65+
// Transfer part of the marketing budget of Album 2 to Album 1.
66+
budget1 += transfer
67+
budget2 -= transfer
68+
const updateSql = "UPDATE Albums " +
69+
"SET MarketingBudget = @budget " +
70+
"WHERE SingerId = @singerId and AlbumId = @albumId"
71+
// Start a DML batch and execute it as part of the current transaction.
72+
if _, err := tx.ExecContext(ctx, "start batch dml"); err != nil {
73+
tx.Rollback()
74+
return err
75+
}
76+
if _, err := tx.ExecContext(ctx, updateSql,
77+
sql.Named("singerId", 1),
78+
sql.Named("albumId", 1),
79+
sql.Named("budget", budget1)); err != nil {
80+
_, _ = tx.ExecContext(ctx, "abort batch")
81+
tx.Rollback()
82+
return err
83+
}
84+
if _, err := tx.ExecContext(ctx, updateSql,
85+
sql.Named("singerId", 2),
86+
sql.Named("albumId", 2),
87+
sql.Named("budget", budget2)); err != nil {
88+
_, _ = tx.ExecContext(ctx, "abort batch")
89+
tx.Rollback()
90+
return err
91+
}
92+
// `run batch` sends the DML statements to Spanner.
93+
// The result contains the total affected rows across the entire batch.
94+
result, err := tx.ExecContext(ctx, "run batch")
95+
if err != nil {
96+
tx.Rollback()
97+
return err
98+
}
99+
if affected, err := result.RowsAffected(); err != nil {
100+
tx.Rollback()
101+
return err
102+
} else if affected != 2 {
103+
// The batch should update 2 rows.
104+
tx.Rollback()
105+
return fmt.Errorf("unexpected number of rows affected: %v", affected)
106+
}
107+
}
108+
// Commit the current transaction.
109+
if err := tx.Commit(); err != nil {
110+
return err
111+
}
112+
113+
fmt.Fprintln(w, "Transferred marketing budget from Album 2 to Album 1")
114+
115+
return nil
116+
}
117+
118+
// [END spanner_dml_getting_started_update]
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_dml_getting_started_insert]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func WriteDataWithDml(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Add 4 rows in one statement.
35+
// The database/sql driver supports positional query parameters.
36+
res, err := db.ExecContext(ctx,
37+
"INSERT INTO Singers (SingerId, FirstName, LastName) "+
38+
"VALUES (?, ?, ?), (?, ?, ?), "+
39+
" (?, ?, ?), (?, ?, ?)",
40+
12, "Melissa", "Garcia",
41+
13, "Russel", "Morales",
42+
14, "Jacqueline", "Long",
43+
15, "Dylan", "Shaw")
44+
if err != nil {
45+
return err
46+
}
47+
c, err := res.RowsAffected()
48+
if err != nil {
49+
return err
50+
}
51+
fmt.Fprintf(w, "%v records inserted\n", c)
52+
53+
return nil
54+
}
55+
56+
// [END spanner_dml_getting_started_insert]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_dml_getting_started_insert]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
_ "github.com/googleapis/go-sql-spanner"
25+
)
26+
27+
func WriteDataWithDmlBatch(ctx context.Context, w io.Writer, databaseName string) error {
28+
db, err := sql.Open("spanner", databaseName)
29+
if err != nil {
30+
return err
31+
}
32+
defer db.Close()
33+
34+
// Add multiple rows in one DML batch.
35+
// database/sql also supports named parameters.
36+
sql := "INSERT INTO Singers (SingerId, FirstName, LastName) " +
37+
"VALUES (@SingerId, @FirstName, @LastName)"
38+
39+
// Get a connection from the pool, so we know that all statements
40+
// are executed on the same connection.
41+
conn, err := db.Conn(ctx)
42+
if err != nil {
43+
return err
44+
}
45+
defer conn.Close()
46+
// `start batch dml` starts a DML batch.
47+
// All following DML statements are buffered in the client until the
48+
// `run batch` statement is executed.
49+
if _, err := conn.ExecContext(ctx, "start batch dml"); err != nil {
50+
return err
51+
}
52+
if _, err := conn.ExecContext(ctx, sql, 16, "Sarah", "Wilson"); err != nil {
53+
return err
54+
}
55+
if _, err := conn.ExecContext(ctx, sql, 17, "Ethan", "Miller"); err != nil {
56+
return err
57+
}
58+
if _, err := conn.ExecContext(ctx, sql, 18, "Maya", "Patel"); err != nil {
59+
return err
60+
}
61+
// `run batch` sends all buffered DML statements in one batch to Spanner.
62+
res, err := conn.ExecContext(ctx, "run batch")
63+
if err != nil {
64+
return err
65+
}
66+
c, err := res.RowsAffected()
67+
if err != nil {
68+
return err
69+
}
70+
71+
if err != nil {
72+
return err
73+
}
74+
fmt.Fprintf(w, "%v records inserted\n", c)
75+
76+
return nil
77+
}
78+
79+
// [END spanner_dml_getting_started_insert]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 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+
// https://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 samples
16+
17+
// [START spanner_insert_data]
18+
import (
19+
"context"
20+
"database/sql"
21+
"fmt"
22+
"io"
23+
24+
"cloud.google.com/go/spanner"
25+
spannerdriver "github.com/googleapis/go-sql-spanner"
26+
)
27+
28+
func WriteDataWithMutations(ctx context.Context, w io.Writer, databaseName string) error {
29+
db, err := sql.Open("spanner", databaseName)
30+
if err != nil {
31+
return err
32+
}
33+
defer db.Close()
34+
35+
// Get a connection so that we can get access to the Spanner specific
36+
// connection interface SpannerConn.
37+
conn, err := db.Conn(ctx)
38+
if err != nil {
39+
return err
40+
}
41+
defer conn.Close()
42+
43+
singerColumns := []string{"SingerId", "FirstName", "LastName"}
44+
albumColumns := []string{"SingerId", "AlbumId", "AlbumTitle"}
45+
mutations := []*spanner.Mutation{
46+
spanner.Insert("Singers", singerColumns, []interface{}{int64(1), "Marc", "Richards"}),
47+
spanner.Insert("Singers", singerColumns, []interface{}{int64(2), "Catalina", "Smith"}),
48+
spanner.Insert("Singers", singerColumns, []interface{}{int64(3), "Alice", "Trentor"}),
49+
spanner.Insert("Singers", singerColumns, []interface{}{int64(4), "Lea", "Martin"}),
50+
spanner.Insert("Singers", singerColumns, []interface{}{int64(5), "David", "Lomond"}),
51+
spanner.Insert("Albums", albumColumns, []interface{}{int64(1), int64(1), "Total Junk"}),
52+
spanner.Insert("Albums", albumColumns, []interface{}{int64(1), int64(2), "Go, Go, Go"}),
53+
spanner.Insert("Albums", albumColumns, []interface{}{int64(2), int64(1), "Green"}),
54+
spanner.Insert("Albums", albumColumns, []interface{}{int64(2), int64(2), "Forever Hold Your Peace"}),
55+
spanner.Insert("Albums", albumColumns, []interface{}{int64(2), int64(3), "Terrified"}),
56+
}
57+
// Mutations can be written outside an explicit transaction using SpannerConn#Apply.
58+
if err := conn.Raw(func(driverConn interface{}) error {
59+
spannerConn, ok := driverConn.(spannerdriver.SpannerConn)
60+
if !ok {
61+
return fmt.Errorf("unexpected driver connection %v, expected SpannerConn", driverConn)
62+
}
63+
_, err = spannerConn.Apply(ctx, mutations)
64+
return err
65+
}); err != nil {
66+
return err
67+
}
68+
fmt.Fprintf(w, "Inserted %v rows\n", len(mutations))
69+
70+
return nil
71+
}
72+
73+
// [END spanner_insert_data]

0 commit comments

Comments
 (0)
Please sign in to comment.