Skip to content

RowEncoder.Row / Rows: encode structs into real *spanner.Row values#2

Merged
apstndb merged 2 commits into
mainfrom
feature/rowencoder-spanner-row
Jun 11, 2026
Merged

RowEncoder.Row / Rows: encode structs into real *spanner.Row values#2
apstndb merged 2 commits into
mainfrom
feature/rowencoder-spanner-row

Conversation

@apstndb

@apstndb apstndb commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add RowEncoder.Row(v T, opts ...EncodeOption) (*spanner.Row, error): encode one struct into a real *spanner.Row (Values followed by spanner.NewRow).
  • Add RowEncoder.Rows(items []T, opts ...EncodeOption) iter.Seq2[*spanner.Row, error]: lazy iterator over encoded rows; yields the first encode error and stops.
  • Sync the godoc API overview, AGENTS.md API map, and add ExampleRowEncoder_Rows rendering a virtual result set through spanvalue.FormatRowSpannerCLICompatible.

Why

Surveyed from spanner-mycli (apstndb/spanner-mycli#657): client-side virtual result sets (SHOW/HELP statements) are best rendered by routing them through the exact same *spanner.Row pipelines used for server query results, instead of duplicating cell-formatting logic on GCV slices. spanner.NewRow passes GenericColumnValue inputs through the client's encodeValue unchanged (Type and Value are used as-is), so the conversion is lossless including typed NULLs — but every consumer currently has to hand-write the GCV → []anyspanner.NewRow dance. Row makes that a one-liner on the compiled encoder; Rows pairs with fallible row-sequence sinks (e.g., a future spanvalue/writer in-memory row runner).

Notes

  • Errors can only come from Values (spanner.NewRow cannot fail for GCV inputs with matching column counts), so Row adds no new error conditions.
  • Rows yields (nil, err) once and stops, matching the abort-on-first-error contract row sinks expect.

Test plan

  • mise run check (fmt-check, vet, build, test, lint)
  • New tests: GCV round-trip equality with Values (including typed NULL), client typed-decode round-trip, ErrNilStructPointer, lazy early-stop, stop-after-error

Row encodes one struct as a *spanner.Row via spanner.NewRow, which passes
GenericColumnValue inputs through the client's encodeValue unchanged (Type
and Value used as-is), so rows carry exactly the encoded GCVs including
typed NULLs. Rows wraps it into a lazy iter.Seq2[*spanner.Row, error] that
yields the first encode error and stops.

This closes the gap surveyed from spanner-mycli's virtual result sets
(SHOW/HELP statements): consumers with *spanner.Row display or export
pipelines previously had to hand-write the GCV -> []any -> spanner.NewRow
dance to route client-side rows through the same code paths as server query
results.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

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

Copy link
Copy Markdown

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 Row and Rows methods to RowEncoder[T], enabling the encoding of Go structs into real *spanner.Row values and lazy iteration over them using Go's iterator protocol. It also includes corresponding documentation, usage examples, and comprehensive unit tests. No review comments were provided, so there is no feedback to address.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@apstndb apstndb left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed with the client source (spanner v1.91.0) and the spanvalue#238 counterpart side by side.

The design is right: Row = Values + spanner.NewRow GCV passthrough keeps the encoder as the single source of encoding semantics, and Rows returning iter.Seq2[*spanner.Row, error] composes exactly with writer.RunRowSeq in apstndb/spanvalue#238 — the producer contract here (yield (nil, err) once, stop) matches the consumer contract there (abort on first yielded error, ignore the paired row, do not consume further). I verified spanner.NewRow does not retain the columnNames slice (it copies names into fresh StructType_Fields), so passing the internal e.columns without the defensive copy Columns() makes is safe.

Two inline comments: one godoc accuracy fix on the passthrough claim, one on test strength for the documented laziness.

Comment thread rowencoder.go Outdated
Comment thread rowencoder_test.go Outdated
…counter

- Row godoc: encodeValue deep-clones GenericColumnValue Type/Value rather
  than using them as-is; state the no-aliasing property explicitly.
- Replace the early-stop subtest with a countingEncoder (spanner.Encoder)
  version that observes EncodeSpanner calls, pinning the documented
  laziness: breaking after the first of two items leaves the second
  unencoded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@apstndb apstndb merged commit 8d2c5ed into main Jun 11, 2026
3 checks passed
@apstndb apstndb deleted the feature/rowencoder-spanner-row branch June 11, 2026 04:47
apstndb added a commit to apstndb/spanner-mycli that referenced this pull request Jun 11, 2026
…line (#661)

SHOW VARIABLES / HELP VARIABLES rows are now defined as Go structs and
rendered by the same code paths as server query results, using
spanenc v0.3.1 and spanvalue v0.7.4 (APIs upstreamed from this work:
apstndb/spanenc#2, apstndb/spanvalue#238):

- spanenc.RowEncoder compiles the row shape once; RowEncoder.Rows encodes
  structs into real *spanner.Row values (lossless GCV passthrough,
  including typed NULLs).
- CSV/JSONL stream through the same spanvalue writers as the query
  streaming path via writer.WriteRowSeq, sharing writer construction
  (newSpanvalueRowIteratorWriterFor).
- Other formats buffer cells through spannerRowToRow/withRawJSONMarker
  with FormatConfig derivation mirroring prepareFormatConfig.
- Table headers carry RowEncoder.ResultSetMetadata row types; verbose mode
  shows column types for virtual result sets.

Behavior changes: verbose headers show types; SHOW VARIABLES result line
now reports "N rows in set" instead of "Empty set" (AffectedRows is now
set); FLOAT64/FLOAT32 display changes with spanvalue v0.7.x
(1.000000 -> 1); SQL export modes keep the buffered table fallback.
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