feat: retrieve read timestamp from read-only transaction#834
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the ReadOnlyTransactionTimestamp method to retrieve the read timestamp of active read-only transactions in Cloud Spanner. The changes span across the connection interface, transaction implementations, in-memory test server, and include a new unit test. Feedback on the changes highlights two improvement opportunities in transaction.go: first, optimizing delegatingTransaction.ReadOnlyTransactionTimestamp to avoid prematurely activating the transaction when it is not yet initialized; second, removing a redundant check for tx.boTx in readOnlyTransaction.ReadOnlyTransactionTimestamp as tx.roTx is guaranteed to be initialized in that scenario.
| func (d *delegatingTransaction) ReadOnlyTransactionTimestamp() (time.Time, error) { | ||
| if err := d.ensureActivated(); err != nil { | ||
| return time.Time{}, err | ||
| } | ||
| return d.contextTransaction.ReadOnlyTransactionTimestamp() | ||
| } |
There was a problem hiding this comment.
Calling d.ensureActivated() here is inefficient because it forces the activation/creation of the underlying Spanner transaction even if no query has been executed yet. Since a transaction cannot have a read timestamp before any query is run, we can avoid this overhead by checking if d.contextTransaction is nil and returning the appropriate error directly based on the transaction's read-only/read-write configuration.
func (d *delegatingTransaction) ReadOnlyTransactionTimestamp() (time.Time, error) {
if d.contextTransaction == nil {
if !propertyTransactionReadOnly.GetValueOrDefault(d.conn.state) {
return time.Time{}, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "cannot retrieve read timestamp on a read-write transaction"))
}
return time.Time{}, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "underlying read-only transaction is not initialized"))
}
return d.contextTransaction.ReadOnlyTransactionTimestamp()
}| func (tx *readOnlyTransaction) ReadOnlyTransactionTimestamp() (time.Time, error) { | ||
| if tx.roTx != nil { | ||
| return tx.roTx.Timestamp() | ||
| } | ||
| if tx.boTx != nil { | ||
| return tx.boTx.Timestamp() | ||
| } | ||
| return time.Time{}, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "underlying read-only transaction is not initialized")) | ||
| } |
There was a problem hiding this comment.
The check if tx.boTx != nil is redundant and represents dead code. When tx.boTx (the batch read-only transaction) is non-nil, tx.roTx is also always initialized to &bo.ReadOnlyTransaction during activation. Therefore, tx.roTx will never be nil in that case, and calling tx.roTx.Timestamp() is sufficient and equivalent to calling tx.boTx.Timestamp().
func (tx *readOnlyTransaction) ReadOnlyTransactionTimestamp() (time.Time, error) {
if tx.roTx != nil {
return tx.roTx.Timestamp()
}
return time.Time{}, spanner.ToSpannerError(status.Error(codes.FailedPrecondition, "underlying read-only transaction is not initialized"))
}|
LGTM. This exposes |
Exposes the ReadOnlyTransactionTimestamp() method on the SpannerConn interface to retrieve the read timestamp of an active read-only or batch read-only transaction. Also corrects mock server behavior to avoid returning read timestamps for read-write transactions.
Fixes #543