Skip to content

fix(spanner): process metadata and stats for PartialResultSets with empty values#428

Open
apstndb wants to merge 1 commit intoyoshidan:mainfrom
apstndb:fix/spanner-plan-mode-metadata
Open

fix(spanner): process metadata and stats for PartialResultSets with empty values#428
apstndb wants to merge 1 commit intoyoshidan:mainfrom
apstndb:fix/spanner-plan-mode-metadata

Conversation

@apstndb
Copy link

@apstndb apstndb commented Feb 28, 2026

Problem

RowIterator::try_recv() returns early when result_set.values.is_empty(), skipping all subsequent processing — metadata, resume_token, and stats are silently discarded.

For reference, the Go client's RowIterator documents the expected availability of these fields:

// The plan for the query. Available after RowIterator.Next returns
// iterator.Done if QueryWithStats was called.
QueryPlan *sppb.QueryPlan

// Execution statistics for the query. Available after RowIterator.Next
// returns iterator.Done if QueryWithStats was called.
QueryStats map[string]interface{}

// The metadata of the results of the query. The metadata are available
// after the first call to RowIterator.Next(), unless the first call to
// RowIterator.Next() returned an error that is not equal to iterator.Done.
Metadata *sppb.ResultSetMetadata

The Rust RowIterator provides equivalent accessors — columns_metadata(), stats() — but they were broken for empty result sets because try_recv() discarded the underlying data before it could be stored.

Affected scenarios

When values are empty (either because the mode returns no rows, or because the query matches 0 rows), all information in the PartialResultSet was discarded. The following table shows what each mode is expected to return and what was lost:

QueryMode When affected Lost information
Plan Always (returns no values) columns_metadata(), stats().query_plan
Profile 0 result rows columns_metadata(), stats().query_plan, stats().query_stats
WithPlanAndStats 0 result rows columns_metadata(), stats().query_plan, stats().query_stats
WithStats 0 result rows columns_metadata(), stats().query_stats
Normal 0 result rows columns_metadata()

Fix

Move the values.is_empty() check after resume_token and stats processing, and call self.rs.add() before returning Ok(false) so that metadata is always captured.

Tests

Added three integration tests in spanner/tests/transaction_ro_test.rs:

  • test_query_plan_mode_metadataQueryMode::Plan populates columns_metadata() despite returning no data rows. On real Spanner, Plan mode also returns stats() with query_plan (plan_nodes), but the emulator does not, so query_plan is not asserted here.
  • test_query_empty_result_metadataQueryMode::Normal with WHERE FALSE still populates columns_metadata().
  • test_query_profile_mode_empty_result_statsQueryMode::Profile with 0 rows populates both columns_metadata() and stats() (query_stats).

All three tests fail without the fix and pass with it. Existing tests pass with no regressions. Additionally, verified against a real Spanner instance that QueryMode::Plan returns stats().query_plan with populated plan_nodes.

…mpty values

Previously, try_recv() returned early when values were empty, skipping
metadata, resume_token, and stats processing. This caused:

1. QueryMode::Plan always returned empty columns_metadata() and lost
   query_plan, because Plan mode responses contain only metadata and
   stats with no values.
2. QueryMode::Profile with 0 result rows lost query_stats.
3. Normal queries returning 0 rows also lost column metadata.

Move the empty-values check after resume_token/stats processing and
call self.rs.add() to ensure metadata is always captured.

Add three integration tests:
- test_query_plan_mode_metadata
- test_query_empty_result_metadata
- test_query_profile_mode_empty_result_stats

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@apstndb apstndb force-pushed the fix/spanner-plan-mode-metadata branch from 5a46899 to 9871a10 Compare February 28, 2026 06:17
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.

1 participant