Skip to content

feat: retrieve read timestamp from read-only transaction#834

Open
olavloite wants to merge 1 commit into
mainfrom
fix/543-readonly-txn-timestamp
Open

feat: retrieve read timestamp from read-only transaction#834
olavloite wants to merge 1 commit into
mainfrom
fix/543-readonly-txn-timestamp

Conversation

@olavloite

@olavloite olavloite commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

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

@olavloite olavloite requested a review from a team as a code owner June 11, 2026 17:35

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread transaction.go
Comment on lines +172 to +177
func (d *delegatingTransaction) ReadOnlyTransactionTimestamp() (time.Time, error) {
if err := d.ensureActivated(); err != nil {
return time.Time{}, err
}
return d.contextTransaction.ReadOnlyTransactionTimestamp()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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()
}

Comment thread transaction.go
Comment on lines +382 to +390
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"))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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"))
}

@olavloite

Copy link
Copy Markdown
Collaborator Author

LGTM. This exposes ReadOnlyTransactionTimestamp() on the SpannerConn interface, allowing clients to query Spanner's chosen read timestamp. Correcting the mock server to not return read timestamps for read-write transactions is a great fix for accuracy. Good test coverage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Method to retrieve spanner.ReadOnlyTransaction.Timestamp

1 participant