Skip to content

Commit 29e69c7

Browse files
egonelbreolavloite
andauthored
feat: expose underlying *spanner.Client (#313)
* feat: expose underlying *spanner.Client Sometimes the exposed features of go-sql-spanner are not sufficient to get the necessary response. This adds an escape hatch to access the underlying client. Fixes #312 * Update driver.go Co-authored-by: Knut Olav Løite <koloite@gmail.com> * Update driver.go --------- Co-authored-by: Knut Olav Løite <koloite@gmail.com>
1 parent 51a9a54 commit 29e69c7

File tree

2 files changed

+86
-2
lines changed

2 files changed

+86
-2
lines changed

driver.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,15 @@ type SpannerConn interface {
479479
// was executed on the connection, or an error if the connection has not executed a read/write transaction
480480
// that committed successfully. The timestamp is in the local timezone.
481481
CommitTimestamp() (commitTimestamp time.Time, err error)
482+
483+
// UnderlyingClient returns the underlying Spanner client for the database.
484+
// The client cannot be used to access the current transaction or batch on
485+
// this connection. Executing a transaction or batch using the client that is
486+
// returned, does not affect this connection.
487+
// Note that multiple connections share the same Spanner client. Calling
488+
// this function on different connections to the same database, can
489+
// return the same Spanner client.
490+
UnderlyingClient() (client *spanner.Client, err error)
482491
}
483492

484493
type conn struct {
@@ -542,6 +551,10 @@ const (
542551
PartitionedNonAtomic
543552
)
544553

554+
func (c *conn) UnderlyingClient() (*spanner.Client, error) {
555+
return c.client, nil
556+
}
557+
545558
func (c *conn) CommitTimestamp() (time.Time, error) {
546559
if c.commitTs == nil {
547560
return time.Time{}, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "this connection has not executed a read/write transaction that committed successfully"))
@@ -1150,8 +1163,8 @@ var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
11501163
// to those types to mean nil/NULL, just like the Go database/sql package.
11511164
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
11521165
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
1153-
rv.IsNil() &&
1154-
rv.Type().Elem().Implements(valuerReflectType) {
1166+
rv.IsNil() &&
1167+
rv.Type().Elem().Implements(valuerReflectType) {
11551168
return nil, nil
11561169
}
11571170
return vr.Value()

examples/underlying-client/main.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright 2024 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+
"database/sql"
20+
"fmt"
21+
22+
"cloud.google.com/go/spanner"
23+
_ "github.com/googleapis/go-sql-spanner"
24+
spannerdriver "github.com/googleapis/go-sql-spanner"
25+
"github.com/googleapis/go-sql-spanner/examples"
26+
)
27+
28+
// Example of using the underlying *spanner.Client.
29+
func underlyingClient(projectId, instanceId, databaseId string) error {
30+
ctx := context.Background()
31+
db, err := sql.Open("spanner", fmt.Sprintf("projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId))
32+
if err != nil {
33+
return fmt.Errorf("failed to open database connection: %v\n", err)
34+
}
35+
defer db.Close()
36+
37+
conn, err := db.Conn(ctx)
38+
if err != nil {
39+
return err
40+
}
41+
defer conn.Close()
42+
43+
if err := conn.Raw(func(driverConn any) error {
44+
spannerConn, ok := driverConn.(spannerdriver.SpannerConn)
45+
if !ok {
46+
return fmt.Errorf("unexpected driver connection %v, expected SpannerConn", driverConn)
47+
}
48+
client, err := spannerConn.UnderlyingClient()
49+
if err != nil {
50+
return fmt.Errorf("unable to access underlying client: %w", err)
51+
}
52+
53+
row := client.Single().Query(ctx, spanner.Statement{SQL: "SELECT 1"})
54+
return row.Do(func(r *spanner.Row) error {
55+
var value int64
56+
err := r.Columns(&value)
57+
if err != nil {
58+
return fmt.Errorf("failed to read column: %w", err)
59+
}
60+
fmt.Println(value)
61+
return nil
62+
})
63+
}); err != nil {
64+
return err
65+
}
66+
return nil
67+
}
68+
69+
func main() {
70+
examples.RunSampleOnEmulator(underlyingClient)
71+
}

0 commit comments

Comments
 (0)