Skip to content

Conversation

@olavloite
Copy link
Collaborator

Adds support for using a bidirectional gRPC stream for sending requests and receiving responses from SpannerLib.

Also adds support for this to the .NET wrapper and modifies all tests to run using both the previous server-streaming API and the new bidirectional streaming API. The latter is the default for the .NET wrapper, and support for the server-streaming API might be removed in a future version.

Adds some more tests for the .NET wrapper and fixes some issues with multi-statement
SQL strings that return errors for the second (or later) statement.

This change also removes the option to use unary RPCs to fetch results using the gRPC
API in the .NET wrapper, as the performance of that is slower than using a streaming
RPC. This will in a future pull request be replaced with a protocol that uses a
bi-directional gRPC stream.
@olavloite olavloite requested a review from a team as a code owner December 10, 2025 13:20
@olavloite
Copy link
Collaborator Author

/gemini review

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @olavloite, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances SpannerLib by introducing a bidirectional gRPC streaming API. This new approach aims to reduce latency and improve efficiency for clients, particularly the .NET wrapper, by allowing multiple operations to be multiplexed over a single connection. The changes involve extensive updates to protobuf definitions, server-side Go implementations, and the .NET client library to fully leverage this new communication paradigm.

Highlights

  • Bidirectional Streaming API: Introduced a new bidirectional gRPC streaming API (ConnectionStream) to SpannerLib, allowing clients to send multiple requests and receive responses over a single persistent stream for lower latency.
  • .NET Wrapper Integration: The .NET wrapper now fully supports the new bidirectional streaming API, making it the default communication style. This includes new GrpcBidiConnection and updates to GrpcLibSpanner to manage the stream and handle various request types.
  • Unified Request/Response Model: A new ConnectionStreamRequest protobuf message unifies various operations (Execute, ExecuteBatch, BeginTransaction, Commit, Rollback, WriteMutations) into a single stream, with ConnectionStreamResponse providing corresponding structured replies.
  • Stream Continuation for Large Results: Added a ContinueStreaming RPC and associated logic to allow clients to fetch remaining rows of a SQL statement that could not be fully returned in the initial ExecuteResponse on the bidirectional stream.
  • Go API Refinements: The core Go API (spannerlib/api/rows.go) was updated with NextBuffered and a resetBuffer option to optimize row fetching, especially for streaming scenarios.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

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 support for a bidirectional streaming API to SpannerLib, along with updates to the .NET wrapper and associated tests. The changes include new gRPC service methods, message definitions, and client-side implementations to leverage the new streaming capabilities. While the core functionality seems well-implemented, there are a few areas that could be improved for robustness, clarity, and adherence to best practices.

I am having trouble creating individual review comments. Click here to see my feedback.

spannerlib/grpc-server/google/spannerlib/v1/spannerlib.proto (77-78)

high

The num_rows field is marked as REQUIRED, but the C# client (e.g., GrpcBidiConnection.cs line 176) sends prefetchRows which can be 0. The Go server also defaults maxFetchRows to 50 if NumRows is 0. This indicates that 0 is a valid value and the field should not be REQUIRED. Marking a field as REQUIRED when 0 is acceptable can lead to validation errors or unexpected behavior if strict proto validation is applied.

  int64 num_rows = 1;

spannerlib/grpc-server/server_test.go (251-350)

high

The TestExecuteStreamingMultiQuery test case was removed. This test covered scenarios with multiple queries in a single streaming execution. Given the introduction of a new bidirectional streaming API, it's crucial to ensure that multi-query scenarios are still adequately tested, especially with the new API. Removing this test without a clear replacement for the new API might leave a gap in test coverage.

spannerlib/grpc-server/google/spannerlib/v1/spannerlib.proto (150-151)

high

The fetch_options field in NextRequest is marked as REQUIRED. However, the FetchOptions message itself has fields (num_rows, encoding) that are used with default/zero values in the client, which contradicts the REQUIRED behavior. This inconsistency can lead to validation errors or unexpected behavior.

  FetchOptions fetch_options = 2;

spannerlib/grpc-server/google/spannerlib/v1/spannerlib.proto (80-81)

high

The encoding field is marked as REQUIRED. However, its usage in the C# client and Go server does not strictly enforce a non-zero value. If 0 is a valid default or acceptable value, this field should not be REQUIRED.

  int64 encoding = 2;

spannerlib/api/rows.go (95)

medium

Using /*marshalResult=*/ as a comment for a function argument is not idiomatic Go. Go does not support named parameters in function calls in this way. This comment can be confusing and should be removed.

	values, _, err := next(ctx, poolId, connId, rowsId, false, resetBuffer)

spannerlib/api/rows.go (73)

medium

Using /*marshalResult=*/ and /*resetBuffer=*/ as comments for function arguments is not idiomatic Go. Go does not support named parameters in function calls in this way. These comments can be confusing and should be removed.

	_, bytes, err := next(ctx, poolId, connId, rowsId, true, false)

spannerlib/api/rows.go (91)

medium

Using /*resetBuffer=*/ as a comment for a function argument is not idiomatic Go. Go does not support named parameters in function calls in this way. This comment can be confusing and should be removed.

	return nextWithBufferOption(ctx, poolId, connId, rowsId, false)

spannerlib/grpc-server/server.go (230)

medium

The value 50 is a magic number. It would be clearer and more maintainable to define this as a named constant.

	const defaultMaxFetchRows = 50
	maxFetchRows := int64(defaultMaxFetchRows)

spannerlib/grpc-server/server.go (258-264)

medium

The error handling logic here is a bit complex. If an error occurs while fetching rows, and it's the first result set, the function returns nil, err. If it's a subsequent result set, it removes the last result set from the response and sets an error status. This distinction might be confusing. Consider simplifying to either always return the error directly or always return partial results with an error status, depending on the desired behavior for partial success.

spannerlib/grpc-server/server.go (279-285)

medium

Similar to the previous comment, the error handling logic here is complex. If an error occurs while fetching stats, and it's the first result set, the function returns nil, err. If it's a subsequent result set, it removes the last result set from the response and sets an error status. This distinction might be confusing. Consider simplifying to either always return the error directly or always return partial results with an error status, depending on the desired behavior for partial success.

spannerlib/grpc-server/server_test.go (137-138)

medium

The assertion for the actual string value of the row (row.Values[0].GetStringValue()) was removed. This reduces the thoroughness of the test, as it no longer verifies the content of the data, only its presence and count. It's important to verify the actual data to ensure correctness.

		if g, w := row.Values[0].GetStringValue(), fmt.Sprintf("%d", numRows); g != w {
			t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
		}

spannerlib/api/rows.go (82)

medium

Using /*resetBuffer=*/ as a comment for a function argument is not idiomatic Go. Go does not support named parameters in function calls in this way. This comment can be confusing and should be removed.

	return nextWithBufferOption(ctx, poolId, connId, rowsId, true)

spannerlib/grpc-server/server_test.go (435-436)

medium

The assertion for the actual string value of the row (row.Data[0].Values[0].GetStringValue()) was removed. This reduces the thoroughness of the test, as it no longer verifies the content of the data, only its presence and count. It's important to verify the actual data to ensure correctness.

				if g, w := row.Data[0].Values[0].GetStringValue(), fmt.Sprintf("%d", numRows); g != w {
					t.Fatalf("value mismatch\n Got: %v\nWant: %v", g, w)
				}

Copy link
Contributor

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

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 a bidirectional streaming API to SpannerLib and updates the .NET wrapper to support it. The changes are extensive and well-structured, particularly the protobuf definitions and the C# implementation which cleverly abstracts both the old and new streaming models. The new feature is also well-tested in the .NET wrapper.
My review focuses on the Go server implementation and its tests. I've identified a potential performance improvement in the new bidirectional stream handler. Additionally, I've noticed that some tests have been weakened by removing assertions, and one important test case for the existing server-streaming API has been removed entirely. Please see my detailed comments for suggestions.

I am having trouble creating individual review comments. Click here to see my feedback.

spannerlib/grpc-server/server_test.go (251-350)

high

This test for ExecuteStreaming with multiple queries has been removed. While new tests for multi-statement execution have been added for bidirectional streaming, this removal reduces test coverage for the existing server-streaming ExecuteStreaming RPC. Was this intentional? If the feature is still supported, it would be good to keep a test for it.

spannerlib/grpc-server/server.go (256)

medium

For better performance, you could use api.NextBuffered here to reuse buffers. This would be consistent with streamRows which also uses the buffered version.

To do this safely, you'll need to clone the returned row before appending it to the resultSet.Rows slice on line 269, as NextBuffered reuses the same ListValue instance. You can use proto.Clone for this, which will require importing google.golang.org/protobuf/proto.

Example of the change on line 269:

resultSet.Rows = append(resultSet.Rows, proto.Clone(row).(*structpb.ListValue))
row, err := api.NextBuffered(ctx, rows.Connection.Pool.Id, rows.Connection.Id, rows.Id)

spannerlib/grpc-server/server_test.go (137-139)

medium

This assertion, which verifies the content of the returned row, was removed. While the test still checks the number of rows, re-adding a check for the row content would make the test more robust against regressions.

spannerlib/grpc-server/server_test.go (238-240)

medium

This assertion verifying the row content has been removed. Please consider re-adding it to ensure the correctness of the data being returned and make the test more robust.

spannerlib/grpc-server/server_test.go (523-525)

medium

This assertion verifying the row content has been removed. Please consider re-adding it to ensure the correctness of the data being returned and make the test more robust.

@olavloite olavloite force-pushed the spanner-lib-bidi-grpc branch 3 times, most recently from 562e253 to a65ea4a Compare December 10, 2025 14:22
Adds support for using a bidirectional gRPC stream for sending requests and receiving
responses from SpannerLib.

Also adds support for this to the .NET wrapper and modifies all tests to run using both
the previous server-streaming API and the new bidirectional streaming API. The latter
is the default for the .NET wrapper, and support for the server-streaming API might be
removed in a future version.
@olavloite olavloite force-pushed the spanner-lib-bidi-grpc branch from a65ea4a to 058b06d Compare December 10, 2025 14:45
@olavloite
Copy link
Collaborator Author

/gemini review

@olavloite olavloite requested a review from bhatt4982 December 10, 2025 15:15
Copy link
Contributor

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

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 a bidirectional streaming API to SpannerLib for improved performance and latency, along with corresponding updates to the .NET wrapper and tests. The changes are generally well-implemented, but I've identified a few areas for improvement, mainly related to code clarity and consistency. Specifically, I've pointed out some confusing inline comments and suggested refactoring some test code to reduce duplication. Addressing these points will enhance the maintainability and readability of the new features.

Base automatically changed from spanner-lib-improve-dotnet-tests to main December 11, 2025 08:03
bhatt4982
bhatt4982 previously approved these changes Dec 11, 2025
Copy link
Contributor

@bhatt4982 bhatt4982 left a comment

Choose a reason for hiding this comment

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

LGTM...

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.

2 participants