Skip to content
Draft
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
d1d3109
chore: create shared lib for connections
olavloite May 16, 2025
3447ae4
Merge branch 'main' into spanner-lib
olavloite May 29, 2025
4d7aad9
chore: add more features
olavloite Jun 4, 2025
21a1c4a
Merge branch 'main' into spanner-lib
olavloite Jun 10, 2025
93f38ba
feat: add transactions
olavloite Jun 12, 2025
312ff67
docs: add code comments
olavloite Jun 16, 2025
f898b49
Merge branch 'main' into spanner-lib
olavloite Jun 16, 2025
251d4ce
feat: move metadata and stats to separate result sets
olavloite Jun 16, 2025
8ac5e6b
feat: add option to return metadata and stats
olavloite Jun 17, 2025
304082f
Merge branch 'main' into return-metadata-and-stats
olavloite Jun 17, 2025
f9ff983
Merge branch 'return-metadata-and-stats' into spanner-lib
olavloite Jun 18, 2025
2de8b88
feat: support timestampbound for single-use read-only tx
olavloite Jun 19, 2025
6667833
test: add more tests
olavloite Jun 23, 2025
3a1ce86
feat: add mutations support
olavloite Jun 25, 2025
91cf56c
test: add tests for dotnet
olavloite Jun 25, 2025
b819fd9
Merge branch 'main' into spanner-lib
olavloite Jun 25, 2025
3db43be
Merge branch 'main' into spanner-lib
olavloite Jun 26, 2025
872a83d
chore: go mod tidy
olavloite Jun 26, 2025
ce6db93
chore: refactor dotnet lib
olavloite Jun 26, 2025
9ab50f2
chore: use separate classes per arch
olavloite Jun 26, 2025
4b910fb
chore: update description
olavloite Jun 27, 2025
2cf0e32
chore: change libraries to packed content
olavloite Jun 27, 2025
cc9fc07
chore: refactor dotnet lib
olavloite Jun 30, 2025
c25dcee
chore: separate native lib + use standard dotnet runtime setup
olavloite Jun 30, 2025
56d667e
Merge branch 'main' into spanner-lib
olavloite Jun 30, 2025
e83455c
chore: cleanup
olavloite Jun 30, 2025
9a4249b
Merge branch 'main' into spanner-lib
olavloite Jul 2, 2025
9a33505
chore: add benchmark
olavloite Jul 3, 2025
6a6999e
Merge branch 'main' into spanner-lib
olavloite Jul 3, 2025
dc717a3
chore: bump native lib version
olavloite Jul 3, 2025
94a3746
chore: bump version for both dotnet libs
olavloite Jul 3, 2025
bfc8923
refactor: add grpc server
olavloite Jul 8, 2025
aaba345
chore: test with CPP lib
olavloite Aug 5, 2025
5fccbde
Merge branch 'main' into spanner-lib
olavloite Aug 20, 2025
4825aa4
chore: add generic transactional connection state
olavloite Aug 21, 2025
1803771
chore: move connection variables to connection state
olavloite Aug 22, 2025
7f78b3b
chore: parse SET/SHOW statements with simple parser
olavloite Aug 25, 2025
5da0cbe
chore: use a callback to supply tx opts
olavloite Aug 26, 2025
cf68db7
chore: replace ExecOptions for connection variables
olavloite Aug 27, 2025
d7b55fc
Merge branch 'main' into replace-exec-options-with-connection-variables
olavloite Aug 28, 2025
72aa0c8
Merge branch 'main' into replace-exec-options-with-connection-variables
olavloite Aug 28, 2025
abc6e5b
Merge branch 'replace-exec-options-with-connection-variables' into sp…
olavloite Aug 28, 2025
88f2dbf
feat: create/drop database statements
olavloite Aug 30, 2025
bd4e2b4
Merge branch 'create-drop-database' into spanner-lib
olavloite Aug 30, 2025
d0a5fc3
feat: create/drop database statements
olavloite Aug 30, 2025
5d9f46e
Merge branch 'create-drop-database' into spanner-lib
olavloite Aug 30, 2025
2c18d41
Merge branch 'main' into spanner-lib
olavloite Sep 4, 2025
5a193a8
chore: cache parsed statements and remove regex loading
olavloite Sep 5, 2025
617308d
chore: move parser to separate package
olavloite Sep 5, 2025
c299895
Merge branch 'main' into move-parser-to-separate-package
olavloite Sep 6, 2025
8064e3d
chore: cleanup and add comments
olavloite Sep 6, 2025
16dd72e
Merge branch 'move-parser-to-separate-package' into spanner-lib
olavloite Sep 7, 2025
9c99bcb
chore: add more tests
olavloite Sep 8, 2025
448daab
Merge branch 'main' into spanner-lib
olavloite Sep 8, 2025
d3fe254
Merge branch 'main' into spanner-lib
olavloite Sep 8, 2025
dd4ed73
chore: remove the backend package
olavloite Sep 8, 2025
0e5ac05
chore: pull retry_aborts_internally when needed
olavloite Sep 8, 2025
928ee4d
chore: add options for numRows and encoding
olavloite Sep 8, 2025
23f6ea6
Merge branch 'pull-retry-aborts' into spanner-lib
olavloite Sep 8, 2025
ec75601
feat: support BEGIN, COMMIT and ROLLBACK statements
olavloite Sep 9, 2025
2bf6e3e
Merge branch 'tx-statements' into spanner-lib
olavloite Sep 9, 2025
900e003
chore: remove transaction id from api
olavloite Sep 10, 2025
c7c0792
Merge branch 'main' into spanner-lib
olavloite Sep 10, 2025
013a61c
chore: add .NET specification tests
olavloite Sep 11, 2025
f6366d4
chore: remove as many proto files as possible
olavloite Sep 12, 2025
5b272c9
Merge branch 'main' into spanner-lib
olavloite Sep 12, 2025
bd774ef
chore: remove more protos
olavloite Sep 12, 2025
63f4ac6
chore: remove unused code
olavloite Sep 12, 2025
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
39 changes: 39 additions & 0 deletions .github/workflows/spanner-lib-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
on:
push:
branches: [ main ]
pull_request:
name: Spanner Lib Tests
jobs:
test:
strategy:
matrix:
go-version: [1.23.x, 1.24.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Run unit tests
working-directory: spannerlib/exported
run: go test -race -short

build-lib:
strategy:
matrix:
go-version: [1.23.x, 1.24.x]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v4
- name: Build shared lib
working-directory: spannerlib
run: go build -o spannerlib.so -buildmode=c-shared
11 changes: 11 additions & 0 deletions checksum_row_iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ func init() {
gob.Register(structpb.Value_StructValue{})
}

var _ rowIterator = &checksumRowIterator{}

// checksumRowIterator implements rowIterator and keeps track of a running
// checksum for all results that have been seen during the iteration of the
// results. This checksum can be used to verify whether a retry returned the
Expand Down Expand Up @@ -249,3 +251,12 @@ func (it *checksumRowIterator) Stop() {
func (it *checksumRowIterator) Metadata() (*sppb.ResultSetMetadata, error) {
return it.metadata, nil
}

func (it *checksumRowIterator) ResultSetStats() *sppb.ResultSetStats {
// TODO: The Spanner client library should offer an option to get the full
// ResultSetStats, instead of only the RowCount and QueryPlan.
return &sppb.ResultSetStats{
RowCount: &sppb.ResultSetStats_RowCountExact{RowCountExact: it.RowIterator.RowCount},
QueryPlan: it.RowIterator.QueryPlan,
}
}
93 changes: 55 additions & 38 deletions client_side_statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ import (
"context"
"database/sql/driver"
"fmt"
"io"
"regexp"
"strconv"
"strings"
"time"

"cloud.google.com/go/spanner"
"cloud.google.com/go/spanner/apiv1/spannerpb"
"google.golang.org/api/iterator"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
Expand All @@ -48,7 +48,7 @@ import (
type statementExecutor struct {
}

func (s *statementExecutor) ShowCommitTimestamp(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowCommitTimestamp(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
ts, err := c.CommitTimestamp()
var commitTs *time.Time
if err == nil {
Expand All @@ -58,106 +58,106 @@ func (s *statementExecutor) ShowCommitTimestamp(_ context.Context, c *conn, _ st
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowRetryAbortsInternally(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowRetryAbortsInternally(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createBooleanIterator("RetryAbortsInternally", c.RetryAbortsInternally())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowAutoBatchDml(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowAutoBatchDml(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createBooleanIterator("AutoBatchDml", c.AutoBatchDml())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowAutoBatchDmlUpdateCount(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowAutoBatchDmlUpdateCount(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createInt64Iterator("AutoBatchDmlUpdateCount", c.AutoBatchDmlUpdateCount())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowAutoBatchDmlUpdateCountVerification(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowAutoBatchDmlUpdateCountVerification(_ context.Context, c *conn, opts ExecOptions, _ string, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createBooleanIterator("AutoBatchDmlUpdateCountVerification", c.AutoBatchDmlUpdateCountVerification())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowAutocommitDmlMode(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowAutocommitDmlMode(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createStringIterator("AutocommitDMLMode", c.AutocommitDMLMode().String())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowReadOnlyStaleness(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowReadOnlyStaleness(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createStringIterator("ReadOnlyStaleness", c.ReadOnlyStaleness().String())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowExcludeTxnFromChangeStreams(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowExcludeTxnFromChangeStreams(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createBooleanIterator("ExcludeTxnFromChangeStreams", c.ExcludeTxnFromChangeStreams())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowMaxCommitDelay(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowMaxCommitDelay(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createStringIterator("MaxCommitDelay", c.MaxCommitDelay().String())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowTransactionTag(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowTransactionTag(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createStringIterator("TransactionTag", c.TransactionTag())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) ShowStatementTag(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Rows, error) {
func (s *statementExecutor) ShowStatementTag(_ context.Context, c *conn, _ string, opts ExecOptions, _ []driver.NamedValue) (driver.Rows, error) {
it, err := createStringIterator("StatementTag", c.StatementTag())
if err != nil {
return nil, err
}
return &rows{it: it}, nil
return createRows(it, opts), nil
}

func (s *statementExecutor) StartBatchDdl(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) StartBatchDdl(_ context.Context, c *conn, _ string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return c.startBatchDDL()
}

func (s *statementExecutor) StartBatchDml(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) StartBatchDml(_ context.Context, c *conn, _ string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return c.startBatchDML( /* automatic = */ false)
}

func (s *statementExecutor) RunBatch(ctx context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) RunBatch(ctx context.Context, c *conn, _ string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return c.runBatch(ctx)
}

func (s *statementExecutor) AbortBatch(_ context.Context, c *conn, _ string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) AbortBatch(_ context.Context, c *conn, _ string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return c.abortBatch()
}

func (s *statementExecutor) SetRetryAbortsInternally(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetRetryAbortsInternally(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
if params == "" {
return nil, spanner.ToSpannerError(status.Error(codes.InvalidArgument, "no value given for RetryAbortsInternally"))
}
Expand All @@ -168,19 +168,19 @@ func (s *statementExecutor) SetRetryAbortsInternally(_ context.Context, c *conn,
return c.setRetryAbortsInternally(retry)
}

func (s *statementExecutor) SetAutoBatchDml(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetAutoBatchDml(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return setBoolVariable("AutoBatchDml", func(value bool) (driver.Result, error) {
return driver.ResultNoRows, c.SetAutoBatchDml(value)
}, params)
}

func (s *statementExecutor) SetAutoBatchDmlUpdateCount(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetAutoBatchDmlUpdateCount(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return setInt64Variable("AutoBatchDmlUpdateCount", func(value int64) (driver.Result, error) {
return driver.ResultNoRows, c.SetAutoBatchDmlUpdateCount(value)
}, params)
}

func (s *statementExecutor) SetAutoBatchDmlUpdateCountVerification(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetAutoBatchDmlUpdateCountVerification(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
return setBoolVariable("AutoBatchDmlUpdateCountVerification", func(value bool) (driver.Result, error) {
return driver.ResultNoRows, c.SetAutoBatchDmlUpdateCountVerification(value)
}, params)
Expand Down Expand Up @@ -208,7 +208,7 @@ func setInt64Variable(name string, f func(value int64) (driver.Result, error), p
return f(value)
}

func (s *statementExecutor) SetAutocommitDmlMode(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetAutocommitDmlMode(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
if params == "" {
return nil, spanner.ToSpannerError(status.Error(codes.InvalidArgument, "no value given for AutocommitDMLMode"))
}
Expand All @@ -224,7 +224,7 @@ func (s *statementExecutor) SetAutocommitDmlMode(_ context.Context, c *conn, par
return c.setAutocommitDMLMode(mode)
}

func (s *statementExecutor) SetExcludeTxnFromChangeStreams(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetExcludeTxnFromChangeStreams(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
if params == "" {
return nil, spanner.ToSpannerError(status.Error(codes.InvalidArgument, "no value given for ExcludeTxnFromChangeStreams"))
}
Expand All @@ -237,23 +237,23 @@ func (s *statementExecutor) SetExcludeTxnFromChangeStreams(_ context.Context, c

var maxCommitDelayRegexp = regexp.MustCompile(`(?i)^\s*('(?P<duration>(\d{1,19})(s|ms|us|ns))'|(?P<number>\d{1,19})|(?P<null>NULL))\s*$`)

func (s *statementExecutor) SetMaxCommitDelay(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetMaxCommitDelay(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
duration, err := parseDuration(maxCommitDelayRegexp, "max_commit_delay", params)
if err != nil {
return nil, err
}
return c.setMaxCommitDelay(duration)
}

func (s *statementExecutor) SetTransactionTag(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetTransactionTag(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
tag, err := parseTag(params)
if err != nil {
return nil, err
}
return c.setTransactionTag(tag)
}

func (s *statementExecutor) SetStatementTag(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetStatementTag(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
tag, err := parseTag(params)
if err != nil {
return nil, err
Expand All @@ -280,7 +280,7 @@ var maxStalenessRegexp = regexp.MustCompile(`(?i)'(?P<type>MAX_STALENESS)[\t ]+(
var readTimestampRegexp = regexp.MustCompile(`(?i)'(?P<type>READ_TIMESTAMP)[\t ]+(?P<timestamp>(\d{4})-(\d{2})-(\d{2})([Tt](\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?)([Zz]|([+-])(\d{2}):(\d{2})))'`)
var minReadTimestampRegexp = regexp.MustCompile(`(?i)'(?P<type>MIN_READ_TIMESTAMP)[\t ]+(?P<timestamp>(\d{4})-(\d{2})-(\d{2})([Tt](\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?)([Zz]|([+-])(\d{2}):(\d{2})))'`)

func (s *statementExecutor) SetReadOnlyStaleness(_ context.Context, c *conn, params string, _ []driver.NamedValue) (driver.Result, error) {
func (s *statementExecutor) SetReadOnlyStaleness(_ context.Context, c *conn, params string, _ ExecOptions, _ []driver.NamedValue) (driver.Result, error) {
if params == "" {
return nil, spanner.ToSpannerError(status.Error(codes.InvalidArgument, "no value given for ReadOnlyStaleness"))
}
Expand Down Expand Up @@ -369,6 +369,17 @@ func matchesToMap(re *regexp.Regexp, s string) map[string]string {
return matches
}

func createEmptyIterator() *clientSideIterator {
return &clientSideIterator{
metadata: &spannerpb.ResultSetMetadata{
RowType: &spannerpb.StructType{
Fields: []*spannerpb.StructType_Field{},
},
},
rows: []*spanner.Row{},
}
}

// createBooleanIterator creates a row iterator with a single BOOL column with
// one row. This is used for client side statements that return a result set
// containing a BOOL value.
Expand Down Expand Up @@ -414,6 +425,8 @@ func createSingleValueIterator(column string, value interface{}, code spannerpb.
}, nil
}

var _ rowIterator = &clientSideIterator{}

// clientSideIterator implements the rowIterator interface for client side
// statements. All values are created and kept in memory, and this struct
// should only be used for small result sets.
Expand All @@ -426,7 +439,7 @@ type clientSideIterator struct {

func (t *clientSideIterator) Next() (*spanner.Row, error) {
if t.index == len(t.rows) {
return nil, io.EOF
return nil, iterator.Done
}
row := t.rows[t.index]
t.index++
Expand All @@ -442,3 +455,7 @@ func (t *clientSideIterator) Stop() {
func (t *clientSideIterator) Metadata() (*spannerpb.ResultSetMetadata, error) {
return t.metadata, nil
}

func (t *clientSideIterator) ResultSetStats() *spannerpb.ResultSetStats {
return &spannerpb.ResultSetStats{}
}
Loading
Loading