Skip to content

Commit 3dbe157

Browse files
committed
Merge branch 'timestamp-bound-exec-option' into ado-net-driver
2 parents cd2a8f5 + e120e88 commit 3dbe157

File tree

5 files changed

+100
-3
lines changed

5 files changed

+100
-3
lines changed

conn.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,16 @@ func (c *conn) setReadOnlyStaleness(staleness spanner.TimestampBound) (driver.Re
420420
return driver.ResultNoRows, nil
421421
}
422422

423+
func (c *conn) readOnlyStalenessPointer() *spanner.TimestampBound {
424+
val := propertyReadOnlyStaleness.GetConnectionPropertyValue(c.state)
425+
if val == nil || !val.HasValue() {
426+
return nil
427+
}
428+
staleness, _ := val.GetValue()
429+
timestampBound := staleness.(spanner.TimestampBound)
430+
return &timestampBound
431+
}
432+
423433
func (c *conn) IsolationLevel() sql.IsolationLevel {
424434
return propertyIsolationLevel.GetValueOrDefault(c.state)
425435
}
@@ -1205,6 +1215,7 @@ func (c *conn) options(reset bool) (*ExecOptions, error) {
12051215
},
12061216
},
12071217
PartitionedQueryOptions: PartitionedQueryOptions{},
1218+
TimestampBound: c.readOnlyStalenessPointer(),
12081219
}
12091220
if c.tempExecOptions != nil {
12101221
effectiveOptions.merge(c.tempExecOptions)
@@ -1585,6 +1596,9 @@ func (c *conn) Rollback(ctx context.Context) error {
15851596
}
15861597

15871598
func queryInSingleUse(ctx context.Context, c *spanner.Client, statement spanner.Statement, statementInfo *parser.StatementInfo, tb spanner.TimestampBound, options *ExecOptions) *spanner.RowIterator {
1599+
if options.TimestampBound != nil {
1600+
tb = *options.TimestampBound
1601+
}
15881602
return c.Single().WithTimestampBound(tb).QueryWithOptions(ctx, statement, options.QueryOptions)
15891603
}
15901604

conn_with_mockserver_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,34 @@ func TestSetLocalReadLockMode(t *testing.T) {
681681
}
682682
}
683683

684+
func TestTimestampBound(t *testing.T) {
685+
t.Parallel()
686+
687+
db, server, teardown := setupTestDBConnection(t)
688+
defer teardown()
689+
ctx := context.Background()
690+
691+
staleness := spanner.MaxStaleness(10 * time.Second)
692+
row := db.QueryRowContext(ctx, testutil.SelectFooFromBar, ExecOptions{TimestampBound: &staleness})
693+
if row.Err() != nil {
694+
t.Fatal(row.Err())
695+
}
696+
var val int64
697+
if err := row.Scan(&val); err != nil {
698+
t.Fatal(err)
699+
}
700+
701+
requests := server.TestSpanner.DrainRequestsFromServer()
702+
executeRequests := testutil.RequestsOfType(requests, reflect.TypeOf(&spannerpb.ExecuteSqlRequest{}))
703+
if g, w := len(executeRequests), 1; g != w {
704+
t.Fatalf("execute requests count mismatch\n Got: %v\nWant: %v", g, w)
705+
}
706+
request := executeRequests[0].(*spannerpb.ExecuteSqlRequest)
707+
if g, w := request.Transaction.GetSingleUse().GetReadOnly().GetMaxStaleness().GetSeconds(), int64(10); g != w {
708+
t.Fatalf("read staleness mismatch\n Got: %v\nWant: %v", g, w)
709+
}
710+
}
711+
684712
func TestCreateDatabase(t *testing.T) {
685713
t.Parallel()
686714

driver.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ type ExecOptions struct {
173173
TransactionOptions spanner.TransactionOptions
174174
// QueryOptions are the query options that will be used for the statement.
175175
QueryOptions spanner.QueryOptions
176+
// TimestampBound is the timestamp bound that will be used for the statement
177+
// if it is a query outside a transaction. Setting this option will override
178+
// the default TimestampBound that is set on the connection.
179+
TimestampBound *spanner.TimestampBound
176180

177181
// PartitionedQueryOptions are used for partitioned queries, and ignored
178182
// for all other statements.
@@ -246,6 +250,9 @@ func (dest *ExecOptions) merge(src *ExecOptions) {
246250
if src.AutocommitDMLMode != Unspecified {
247251
dest.AutocommitDMLMode = src.AutocommitDMLMode
248252
}
253+
if src.TimestampBound != nil {
254+
dest.TimestampBound = src.TimestampBound
255+
}
249256
if src.PropertyValues != nil {
250257
dest.PropertyValues = append(dest.PropertyValues, src.PropertyValues...)
251258
}

drivers/spanner-ado-net/spanner-ado-net-tests/BasicTests.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.Text.Json;
1717
using Google.Cloud.Spanner.V1;
1818
using Google.Cloud.SpannerLib.MockServer;
19+
using Google.Protobuf.WellKnownTypes;
1920

2021
namespace Google.Cloud.Spanner.DataProvider.Tests;
2122

@@ -66,6 +67,34 @@ public void TestExecuteParameterizedQuery()
6667
Assert.That(reader.GetInt64(0), Is.EqualTo(1));
6768
}
6869
}
70+
71+
[Test]
72+
public void TestExecuteStaleQuery()
73+
{
74+
using var connection = new SpannerConnection();
75+
connection.ConnectionString = ConnectionString;
76+
connection.Open();
77+
78+
using var cmd = connection.CreateCommand();
79+
cmd.CommandText = "SELECT 1";
80+
cmd.SingleUseReadOnlyTransactionOptions = new TransactionOptions.Types.ReadOnly
81+
{
82+
ExactStaleness = Duration.FromTimeSpan(TimeSpan.FromSeconds(10)),
83+
};
84+
using var reader = cmd.ExecuteReader();
85+
while (reader.Read())
86+
{
87+
Assert.That(reader.GetInt64(0), Is.EqualTo(1));
88+
}
89+
90+
var request = Fixture.SpannerMock.Requests.OfType<ExecuteSqlRequest>().First();
91+
Assert.That(request, Is.Not.Null);
92+
Assert.That(request.Transaction, Is.Not.Null);
93+
Assert.That(request.Transaction.SingleUse, Is.Not.Null);
94+
Assert.That(request.Transaction.SingleUse.ReadOnly, Is.Not.Null);
95+
Assert.That(request.Transaction.SingleUse.ReadOnly.ExactStaleness, Is.Not.Null);
96+
Assert.That(request.Transaction.SingleUse.ReadOnly.ExactStaleness.Seconds, Is.EqualTo(10));
97+
}
6998

7099
[Test]
71100
public void TestInsertAllDataTypes()

spannerlib/api/connection.go

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,8 @@ func extractParams(directExecuteContext context.Context, statement *spannerpb.Ex
438438
}
439439
params := make([]any, paramsLen)
440440
params = append(params, spannerdriver.ExecOptions{
441-
DecodeOption: spannerdriver.DecodeOptionProto,
442-
// TODO: Implement support for passing in stale query options
443-
// TimestampBound: extractTimestampBound(statement),
441+
DecodeOption: spannerdriver.DecodeOptionProto,
442+
TimestampBound: extractTimestampBound(statement),
444443
ReturnResultSetMetadata: true,
445444
ReturnResultSetStats: true,
446445
DirectExecuteQuery: true,
@@ -470,6 +469,26 @@ func extractParams(directExecuteContext context.Context, statement *spannerpb.Ex
470469
return params
471470
}
472471

472+
func extractTimestampBound(statement *spannerpb.ExecuteSqlRequest) *spanner.TimestampBound {
473+
if statement.Transaction != nil && statement.Transaction.GetSingleUse() != nil && statement.Transaction.GetSingleUse().GetReadOnly() != nil {
474+
ro := statement.Transaction.GetSingleUse().GetReadOnly()
475+
var t spanner.TimestampBound
476+
if ro.GetStrong() {
477+
t = spanner.StrongRead()
478+
} else if ro.GetMaxStaleness() != nil {
479+
t = spanner.MaxStaleness(ro.GetMaxStaleness().AsDuration())
480+
} else if ro.GetExactStaleness() != nil {
481+
t = spanner.ExactStaleness(ro.GetExactStaleness().AsDuration())
482+
} else if ro.GetMinReadTimestamp() != nil {
483+
t = spanner.MinReadTimestamp(ro.GetMinReadTimestamp().AsTime())
484+
} else if ro.GetReadTimestamp() != nil {
485+
t = spanner.ReadTimestamp(ro.GetReadTimestamp().AsTime())
486+
}
487+
return &t
488+
}
489+
return nil
490+
}
491+
473492
func determineBatchType(conn *Connection, statements []*spannerpb.ExecuteBatchDmlRequest_Statement) (parser.BatchType, error) {
474493
if len(statements) == 0 {
475494
return parser.BatchTypeDdl, status.Errorf(codes.InvalidArgument, "cannot determine type of an empty batch")

0 commit comments

Comments
 (0)