Skip to content

Commit 4099da8

Browse files
authored
Add support for Directed reads (#163)
* Add option for Directed Read * Add test and README for Directed reads * Explicitly set query-level directed read options for read-write transactions. * Bump up Go version to 1.19
1 parent 4ad906e commit 4099da8

9 files changed

Lines changed: 315 additions & 1377 deletions

File tree

.github/workflows/run-tests.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
- uses: actions/checkout@v2
3434
- uses: actions/setup-go@v2
3535
with:
36-
go-version: '1.18'
36+
go-version: '1.19'
3737
- run: go version
3838
- run: make setup-emulator
3939
env:

README.md

Lines changed: 46 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,20 @@ Usage:
3131
spanner-cli [OPTIONS]
3232
3333
spanner:
34-
-p, --project= (required) GCP Project ID. [$SPANNER_PROJECT_ID]
35-
-i, --instance= (required) Cloud Spanner Instance ID [$SPANNER_INSTANCE_ID]
36-
-d, --database= (required) Cloud Spanner Database ID. [$SPANNER_DATABASE_ID]
37-
-e, --execute= Execute SQL statement and quit.
38-
-f, --file= Execute SQL statement from file and quit.
39-
-t, --table Display output in table format for batch mode.
40-
-v, --verbose Display verbose output.
41-
--credential= Use the specific credential file
42-
--prompt= Set the prompt to the specified format
43-
--history= Set the history file to the specified path
44-
--priority= Set default request priority (HIGH|MEDIUM|LOW)
45-
--role= Use the specific database role
34+
-p, --project= (required) GCP Project ID. [$SPANNER_PROJECT_ID]
35+
-i, --instance= (required) Cloud Spanner Instance ID [$SPANNER_INSTANCE_ID]
36+
-d, --database= (required) Cloud Spanner Database ID. [$SPANNER_DATABASE_ID]
37+
-e, --execute= Execute SQL statement and quit.
38+
-f, --file= Execute SQL statement from file and quit.
39+
-t, --table Display output in table format for batch mode.
40+
-v, --verbose Display verbose output.
41+
--credential= Use the specific credential file
42+
--prompt= Set the prompt to the specified format
43+
--history= Set the history file to the specified path
44+
--priority= Set default request priority (HIGH|MEDIUM|LOW)
45+
--role= Use the specific database role
46+
--directed-read= Directed read option (replica_location:replica_type).
47+
The replicat_type is optional and either READ_ONLY or READ_WRITE.
4648
4749
Help Options:
4850
-h, --help Show this help message
@@ -144,6 +146,38 @@ $ spanner-cli -p myproject -i myinstance -d mydb -e 'SELECT * FROM users;' -t
144146
+----+------+--------+
145147
```
146148

149+
### Directed reads mode
150+
151+
spanner-cli now supports directed reads, a feature that allows you to read data from a specific replica of a Spanner database.
152+
To use directed reads with spanner-cli, you need to specify the `--directed-read` flag.
153+
The `--directed-read` flag takes a single argument, which is the name of the replica that you want to read from.
154+
The replica name can be specified in one of the following formats:
155+
156+
- `<replica_location>`
157+
- `<replica_location>:<replica_type>`
158+
159+
The `<replica_location>` specifies the region where the replica is located such as `us-central1`, `asia-northeast2`.
160+
The `<replica_type>` specifies the type of the replica either `READ_WRITE` or `READ_ONLY`.
161+
162+
```
163+
$ spanner-cli -p myproject -i myinstance -d mydb --directed-read us-central1
164+
165+
$ spanner-cli -p myproject -i myinstance -d mydb --directed-read us-central1:READ_ONLY
166+
167+
$ spanner-cli -p myproject -i myinstance -d mydb --directed-read asia-northeast2:READ_WRITE
168+
```
169+
170+
Directed reads are only effective for single queries or queries within a read-only transaction.
171+
Please note that directed read options do not apply to queries within a read-write transaction.
172+
173+
> [!NOTE]
174+
> If you specify an incorrect region or type for directed reads, directed reads will not be enabled and [your requsts won't be routed as expected](https://cloud.google.com/spanner/docs/directed-reads#parameters). For example, in a multi-region configuration `nam3`, if you mistype `us-east1` as `us-east-1`, the connection will succeed, but directed reads will not be enabled.
175+
>
176+
> To perform directed reads to `asia-northeast2` in a multi-region configuration `asia1`, you need to specify `asia-northeast2` or `asia-northeast2:READ_WRITE`.
177+
> Since the replicas placed in `asia-northeast2` are READ_WRITE replicas, directed reads will not be enabled if you specify `asia-northeast2:READ_ONLY`.
178+
>
179+
> Please refer to [the Spanner documentation](https://cloud.google.com/spanner/docs/instance-configurations#available-configurations-multi-region) to verify the valid configurations.
180+
147181
## Syntax
148182

149183
In the following syntax, we use `<>` for a placeholder, `[]` for an optional keyword,

cli.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ type command struct {
7575
Vertical bool
7676
}
7777

78-
func NewCli(projectId, instanceId, databaseId, prompt, historyFile string, credential []byte, inStream io.ReadCloser, outStream io.Writer, errStream io.Writer, verbose bool, priority pb.RequestOptions_Priority, role string, endpoint string) (*Cli, error) {
79-
session, err := createSession(projectId, instanceId, databaseId, credential, priority, role, endpoint)
78+
func NewCli(projectId, instanceId, databaseId, prompt, historyFile string, credential []byte, inStream io.ReadCloser, outStream io.Writer, errStream io.Writer, verbose bool, priority pb.RequestOptions_Priority, role string, endpoint string, directedRead *pb.DirectedReadOptions) (*Cli, error) {
79+
session, err := createSession(projectId, instanceId, databaseId, credential, priority, role, endpoint, directedRead)
8080
if err != nil {
8181
return nil, err
8282
}
@@ -148,7 +148,7 @@ func (c *Cli) RunInteractive() int {
148148
}
149149

150150
if s, ok := stmt.(*UseStatement); ok {
151-
newSession, err := createSession(c.Session.projectId, c.Session.instanceId, s.Database, c.Credential, c.Priority, s.Role, c.Endpoint)
151+
newSession, err := createSession(c.Session.projectId, c.Session.instanceId, s.Database, c.Credential, c.Priority, s.Role, c.Endpoint, c.Session.directedRead)
152152
if err != nil {
153153
c.PrintInteractiveError(err)
154154
continue
@@ -310,15 +310,15 @@ func (c *Cli) getInterpolatedPrompt() string {
310310
return prompt
311311
}
312312

313-
func createSession(projectId string, instanceId string, databaseId string, credential []byte, priority pb.RequestOptions_Priority, role string, endpoint string) (*Session, error) {
313+
func createSession(projectId string, instanceId string, databaseId string, credential []byte, priority pb.RequestOptions_Priority, role string, endpoint string, directedRead *pb.DirectedReadOptions) (*Session, error) {
314314
var opts []option.ClientOption
315315
if credential != nil {
316316
opts = append(opts, option.WithCredentialsJSON(credential))
317317
}
318318
if endpoint != "" {
319319
opts = append(opts, option.WithEndpoint(endpoint))
320320
}
321-
return NewSession(projectId, instanceId, databaseId, priority, role, opts...)
321+
return NewSession(projectId, instanceId, databaseId, priority, role, directedRead, opts...)
322322
}
323323

324324
func readInteractiveInput(rl *readline.Instance, prompt string) (*inputStatement, error) {

go.mod

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,63 @@
11
module github.com/cloudspannerecosystem/spanner-cli
22

3-
go 1.18
3+
go 1.19
44

55
require (
6-
cloud.google.com/go v0.110.0
7-
cloud.google.com/go/spanner v1.44.0
6+
cloud.google.com/go v0.111.0
7+
cloud.google.com/go/spanner v1.55.0
88
github.com/apstndb/gsqlsep v0.0.0-20230324124551-0e8335710080
99
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
10-
github.com/google/go-cmp v0.5.9
10+
github.com/google/go-cmp v0.6.0
1111
github.com/jessevdk/go-flags v1.4.0
1212
github.com/olekukonko/tablewriter v0.0.4
1313
github.com/xlab/treeprint v1.0.1-0.20200715141336-10e0bc383e01
14-
google.golang.org/api v0.111.0
15-
google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488
16-
google.golang.org/grpc v1.53.0
17-
google.golang.org/protobuf v1.28.1
14+
google.golang.org/api v0.155.0
15+
google.golang.org/genproto v0.0.0-20231212172506-995d672761c0
16+
google.golang.org/grpc v1.60.1
17+
google.golang.org/protobuf v1.32.0
1818
)
1919

2020
require (
21-
cloud.google.com/go/compute v1.18.0 // indirect
21+
cloud.google.com/go/compute v1.23.3 // indirect
2222
cloud.google.com/go/compute/metadata v0.2.3 // indirect
23-
cloud.google.com/go/iam v0.12.0 // indirect
24-
cloud.google.com/go/longrunning v0.4.1 // indirect
23+
cloud.google.com/go/iam v1.1.5 // indirect
24+
cloud.google.com/go/longrunning v0.5.4 // indirect
2525
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
2626
github.com/cespare/xxhash/v2 v2.2.0 // indirect
2727
github.com/chzyer/logex v1.1.10 // indirect
2828
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
2929
github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect
30-
github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43 // indirect
31-
github.com/envoyproxy/go-control-plane v0.11.0 // indirect
32-
github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect
30+
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect
31+
github.com/envoyproxy/go-control-plane v0.11.1 // indirect
32+
github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect
33+
github.com/felixge/httpsnoop v1.0.4 // indirect
34+
github.com/go-logr/logr v1.3.0 // indirect
35+
github.com/go-logr/stdr v1.2.2 // indirect
3336
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
34-
github.com/golang/protobuf v1.5.2 // indirect
35-
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
36-
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
37+
github.com/golang/protobuf v1.5.3 // indirect
38+
github.com/google/s2a-go v0.1.7 // indirect
39+
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
40+
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
41+
github.com/json-iterator/go v1.1.12 // indirect
3742
github.com/mattn/go-runewidth v0.0.8 // indirect
43+
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
44+
github.com/modern-go/reflect2 v1.0.2 // indirect
3845
go.opencensus.io v0.24.0 // indirect
46+
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1 // indirect
47+
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
48+
go.opentelemetry.io/otel v1.21.0 // indirect
49+
go.opentelemetry.io/otel/metric v1.21.0 // indirect
50+
go.opentelemetry.io/otel/trace v1.21.0 // indirect
51+
golang.org/x/crypto v0.17.0 // indirect
3952
golang.org/x/exp v0.0.0-20230310171629-522b1b587ee0 // indirect
40-
golang.org/x/net v0.8.0 // indirect
41-
golang.org/x/oauth2 v0.6.0 // indirect
42-
golang.org/x/sys v0.6.0 // indirect
43-
golang.org/x/text v0.8.0 // indirect
44-
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
45-
google.golang.org/appengine v1.6.7 // indirect
53+
golang.org/x/net v0.19.0 // indirect
54+
golang.org/x/oauth2 v0.15.0 // indirect
55+
golang.org/x/sync v0.5.0 // indirect
56+
golang.org/x/sys v0.15.0 // indirect
57+
golang.org/x/text v0.14.0 // indirect
58+
golang.org/x/time v0.5.0 // indirect
59+
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
60+
google.golang.org/appengine v1.6.8 // indirect
61+
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 // indirect
62+
google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 // indirect
4663
)

0 commit comments

Comments
 (0)