Documentation
¶
Overview ¶
Package spaniter adapts Cloud Spanner row streams to Go standard iterators.
The package is intentionally lower-level than formatters and writers: it owns only iterator lifecycle concerns such as RowIterator.Stop, result metadata, and post-drain query stats. Formatting, headers, and export policy stay in callers.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNilRow = errors.New("nil row")
ErrNilRow reports that an adapted source produced a nil row with a nil error.
var ErrNilRowIterator = errors.New("nil row iterator")
ErrNilRowIterator reports that RowIteratorSeq was given a nil iterator.
Because RowIteratorSeq returns an iter.Seq2, the error is yielded when the sequence is consumed rather than returned by the constructor.
Functions ¶
func RowIteratorSeq ¶
RowIteratorSeq adapts a cloud.google.com/go/spanner.RowIterator to a Go standard iterator.
The returned sequence owns rowIter: once iteration starts it always calls *cloud.google.com/go/spanner.RowIterator.Stop before returning. Metadata and stats are exposed through WithOnMetadata and WithOnStats hooks instead of requiring callers to keep reading fields from the stopped RowIterator. The sequence is single-use and not safe for concurrent consumption; construct a new RowIterator for another pass.
If the returned sequence is never invoked, RowIteratorSeq cannot call Stop. After constructing a sequence, callers must either consume it, pass it to code that will consume or stop it, or retain responsibility for stopping the original RowIterator.
Each yielded pair is either a non-nil row with a nil error, or a nil row with a non-nil terminal error. After yielding a non-nil error, the sequence stops. Consumers should stop processing and return or break on the first non-nil error. On terminal errors, WithResult contains only lifecycle data observed before the error, and WithOnStats is not called.
func Rows ¶
Rows adapts already-built rows to the fallible sequence shape used by RowIteratorSeq. Non-nil rows are yielded with a nil error. A nil row aborts the sequence by yielding ErrNilRow.
Row sources that can fail per row should produce their own iter.Seq2 instead of pre-building a slice for Rows.
func SliceToRowSeq ¶
SliceToRowSeq adapts an existing row slice to the fallible sequence shape used by RowIteratorSeq.
It exists for downstream tests, fakes, and virtual result sets that naturally store fixtures as []*spanner.Row. It is equivalent to Rows(rows...), including nil-row handling: nil rows yield ErrNilRow and abort the sequence.
Types ¶
type Option ¶
type Option func(*config)
Option configures RowIteratorSeq and DrainRowIterator.
func WithDrainOnEarlyStop ¶
func WithDrainOnEarlyStop() Option
WithDrainOnEarlyStop configures RowIteratorSeq to consume the remaining rows after the consumer stops early.
Draining is disabled by default to preserve normal iterator early-exit behavior. Use this option when callers need WithOnStats to run after any early stop, including a range-loop break or an adapter that stops pulling because a downstream operation failed. Errors encountered only during this post-stop drain cannot be yielded to the caller and therefore suppress the stats hook. It has no effect on DrainRowIterator, which always drains.
func WithOnMetadata ¶
func WithOnMetadata(f func(*sppb.ResultSetMetadata)) Option
WithOnMetadata registers a hook that runs once when result metadata becomes available.
For a query with rows, the hook runs after the first successful Next call and before that first row is yielded, so metadata captured by the hook is visible inside the first loop body. For an empty result set, the hook runs after Next returns iterator.Done. A nil hook is ignored.
func WithOnStats ¶
WithOnStats registers a hook that runs after the adapted iterator has reached iterator.Done and has been stopped.
If the consumer stops early, stats are available only when WithDrainOnEarlyStop is also configured. A nil hook is ignored.
func WithResult ¶
func WithResult(result *RowIteratorResult) Option
WithResult stores iterator lifecycle data in result as it becomes available.
The pointed value is reset when iteration starts. Metadata is set before the first row is yielded, RowsRead is updated after each consumed row, and Stats is set only after the iterator reaches iterator.Done. On errors, result contains the partial lifecycle data observed before the error. A nil result is ignored.
type RowIteratorResult ¶
type RowIteratorResult struct {
Metadata *sppb.ResultSetMetadata
Stats Stats
RowsRead int64
}
RowIteratorResult is the metadata and stats available from a cloud.google.com/go/spanner.RowIterator.
RowsRead counts rows consumed from the iterator. Metadata and Stats values are not deep-copied from the underlying RowIterator; treat returned maps and protos as read-only.
func DrainRowIterator ¶
func DrainRowIterator(rowIter *spanner.RowIterator, opts ...Option) (*RowIteratorResult, error)
DrainRowIterator consumes rowIter to iterator.Done without yielding rows.
The helper owns rowIter and always calls *cloud.google.com/go/spanner.RowIterator.Stop before returning. It is useful when callers need result metadata, query stats, query plan, or DML row count but do not want to expose row values to application code. If iteration fails, the returned result can be non-nil and contain partial metadata and RowsRead observed before the error; stats are only populated after a successful drain to iterator.Done.
Cloud Spanner only populates metadata after the first Next call, and stats after Next returns iterator.Done. DrainRowIterator therefore still consumes the result stream internally; it does not ask Spanner for stats without reading the stream. To avoid reading data rows at the query level, callers must execute a statement that returns no data rows.
type Stats ¶
Stats holds execution information populated on a cloud.google.com/go/spanner.RowIterator after the iterator reaches iterator.Done.
QueryPlan and QueryStats are set when the query used QueryWithStats. RowCount holds the DML row count after iterator.Done. Values are not deep-copied from the underlying RowIterator; treat returned maps and protos as read-only.
Stats mirrors the public fields exposed by cloud.google.com/go/spanner.RowIterator. Use Stats.ResultSetStats when downstream code needs the protobuf ResultSetStats shape. The Go client has already decoded query stats to a map and exposes row count as a single int64, so Stats cannot distinguish an absent row count from row_count_exact:0. The Go client's PartitionedUpdate APIs return counts directly rather than through RowIterator, so partitioned DML counts are outside this type's normal scope.
func (Stats) HasResultSetStats ¶ added in v0.1.1
HasResultSetStats reports whether Stats.ResultSetStats would encode any field.
This is useful when callers build an enclosing ResultSet and want to omit the stats field when the default conversion would be empty. It returns false for DML row_count_exact:0 because Stats cannot distinguish exact zero from an absent row count. Callers that know the Stats came from DML and need row_count_exact:0 should call Stats.ResultSetStatsForDML directly.
func (Stats) ResultSetStats ¶ added in v0.1.1
func (s Stats) ResultSetStats() (*sppb.ResultSetStats, error)
ResultSetStats returns s in Cloud Spanner protobuf ResultSetStats form.
Most callers should use this method. The row-count caveat below matters only to consumers that distinguish an absent row_count from row_count_exact:0; code that treats an absent row_count as the zero value sees the same count either way.
QueryPlan is reused, QueryStats is re-encoded as a protobuf Struct, and a non-zero RowCount is encoded as row_count_exact. RowIterator exposes row count as a plain int64, so an absent row count and an exact zero row count are indistinguishable; this method omits row count when RowCount is zero. Callers that know RowCount is a DML count can use Stats.ResultSetStatsForDML.
ResultSetStats returns an error if QueryStats contains a key or value that cannot be represented by structpb.Struct.
func (Stats) ResultSetStatsForDML ¶ added in v0.1.1
func (s Stats) ResultSetStatsForDML() (*sppb.ResultSetStats, error)
ResultSetStatsForDML returns s as ResultSetStats for DML and always encodes RowCount as row_count_exact, including zero.
This method is for consumers that need protobuf oneof presence to distinguish DML row_count_exact:0 from an absent row_count. If the consumer treats an absent row_count as the zero value, Stats.ResultSetStats is sufficient.
Use this method only when the caller knows the Stats came from DML. Using it for query stats creates a misleading row_count_exact field even though the Spanner API would omit row_count for queries. Using Stats.ResultSetStats for DML is fine for non-zero row counts, but loses the distinction between absent row count and row_count_exact:0. Partitioned DML is outside spaniter's RowIterator path because the Go client exposes it through Client.PartitionedUpdate, not RowIterator.
ResultSetStatsForDML returns an error if QueryStats contains a key or value that cannot be represented by structpb.Struct.