RowEncoder.Row / Rows: encode structs into real *spanner.Row values#2
Conversation
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>
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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.
…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>
…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.
Summary
RowEncoder.Row(v T, opts ...EncodeOption) (*spanner.Row, error): encode one struct into a real*spanner.Row(Valuesfollowed byspanner.NewRow).RowEncoder.Rows(items []T, opts ...EncodeOption) iter.Seq2[*spanner.Row, error]: lazy iterator over encoded rows; yields the first encode error and stops.ExampleRowEncoder_Rowsrendering a virtual result set throughspanvalue.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.Rowpipelines used for server query results, instead of duplicating cell-formatting logic on GCV slices.spanner.NewRowpassesGenericColumnValueinputs through the client'sencodeValueunchanged (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 →[]any→spanner.NewRowdance.Rowmakes that a one-liner on the compiled encoder;Rowspairs with fallible row-sequence sinks (e.g., a futurespanvalue/writerin-memory row runner).Notes
Values(spanner.NewRowcannot fail for GCV inputs with matching column counts), soRowadds no new error conditions.Rowsyields(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)Values(including typed NULL), client typed-decode round-trip,ErrNilStructPointer, lazy early-stop, stop-after-error