Documentation
¶
Overview ¶
Package writer streams Spanner query results to delimited text, JSONL, or SQL INSERT using github.com/apstndb/spanvalue formatters.
Main types: DelimitedWriter, JSONLWriter, SQLInsertWriter, and the Writer / FlushWriter interfaces. Register column schema with WithColumnNames, WithRowType, or WithMetadata (or DelimitedWriter.PrepareRowType / DelimitedWriter.PrepareColumnNames after construction). DelimitedWriter buffers through encoding/csv—call Flusher.Flush after the final row, or pass WithFlushEachRow for per-row flush during streaming.
Extended documentation, RowIterator recipes, and module-split notes: https://github.com/apstndb/spanvalue/blob/main/writer/README.md
RowIterator ¶
WriteRowIterator targets built-in RowIteratorWriter implementations (DelimitedWriter, JSONLWriter, SQLInsertWriter) via RowIteratorHooksFromWriter. RunRowIterator is the extension point for other sinks: supply RowIteratorHooks built with NewRowIteratorHooks and the With* setters, or decorate with WithRowOrdinal, ObserveWriteRow, and AfterEachSuccessfulWriteRow. Both helpers own the iterator they receive: they consume it, call *cloud.google.com/go/spanner.RowIterator.Stop, and return RowIteratorResult (metadata, stats, RowIteratorResult.RowsRead). Prefer passing a newly created iterator directly (for example txn.Query(ctx, stmt)); do not defer Stop at the call site. Use the returned result for post-run metadata and stats.
For manual *cloud.google.com/go/spanner.RowIterator.Next loops, bind the iterator, defer Stop, register RowIteratorWriter.PrepareRowType after the first Next when results may be empty, and read metadata or stats only after consuming to google.golang.org/api/iterator.Done; propagate Flusher.Flush errors (do not defer Flush).
Rows that do not come from a RowIterator — client-side (virtual) result sets or locally constructed rows — go through WriteRowSeq and RunRowSeq, which take explicit metadata plus a fallible row sequence (RowSeq adapts pre-built rows) and share the RunRowIterator hook contract. When the row type is only known after producing begins (merged concurrent sources such as partitioned-query fan-in), RunRowSeqDeferredMetadata accepts a metadata func evaluated after the first pull, so producers publish the row type before their first yield instead of holding rows back.
Direct writers vs hooks ¶
Prefer WriteRowIterator when the destination is a package writer. Prefer RunRowIterator when formatting or export logic should stay outside spanvalue. Prefer Writer.WriteRow or *DelimitedWriter.WriteGCVs without RowIterator when the app already owns iteration (for example database/sql scans—see the root README go-sql-spanner section).
DelimitedGCVExportOptions and JSONLGCVExportOptions group metadata, formatter, and unnamed-field namer options for GCV slice export. WithFormatter does not call *spanvalue.FormatConfig.Validate; validate hand-built formatters before construction.
Quoted delimited text vs raw tab-separated ¶
DelimitedWriter uses encoding/csv rules (RFC 4180-style). NewDelimitedWriter with delimiter '\t' produces quoted TSV, not a raw join of formatted strings. Legacy raw TAB export can implement Writer or RowIteratorWriter and join columns with '\t'.
Column names and registered schema ¶
*DelimitedWriter.WriteGCVs and *DelimitedWriter.WriteStructValues require prior name/type registration. Writer.WriteRow and *DelimitedWriter.WriteValues supply names per call. Writers distinguish missing schema from registered zero-column schema; see ErrMissingColumnNames. WithMetadata from a cloud.google.com/go/spanner.RowIterator before the first Next registers an empty schema—use RowIteratorWriter.PrepareRowType after the first Next or WriteRowIterator instead.
Write errors ¶
All three writers latch the first error caused by writing to the underlying io.Writer (the encoding/csv Writer.Error pattern): after any Write* or Flusher.Flush call fails with an output error, every subsequent Write* or Flush call returns that first error and no further output is attempted. Discard the writer after any write error. Validation errors reported before output is attempted (for example ErrMissingColumnNames or ErrColumnNamesMismatch) are not latched.
SQL INSERT ¶
NewSQLInsertWriter accepts WithSQLInsertKind, WithSQLDialect, and WithSQLBatchSize. It rejects an empty table name (after strings.TrimSpace) at construction with ErrEmptyTableName and an out-of-range SQLInsertKind with ErrInvalidSQLInsertKind. Qualified names with empty segments are rejected on the first write with ErrEmptyTableName. Each statement is emitted with a single Write; batched rows are buffered until the multi-row statement completes. After any write error from SQLInsertWriter, discard the writer; later calls return the latched error (see "Write errors"). *SQLInsertWriter.Flush closes a partial batch when batching.
Index ¶
- Constants
- Variables
- func FormatDelimitedRow(fc *spanvalue.FormatConfig, row *spanner.Row, delimiter rune) (string, error)
- func FormatDelimitedValues(fc *spanvalue.FormatConfig, columnNames []string, ...) (string, error)
- func FormatJSONLRow(fc *spanvalue.FormatConfig, row *spanner.Row, ...) (string, error)
- func FormatJSONLValues(fc *spanvalue.FormatConfig, columnNames []string, ...) (string, error)
- func RowData(row *spanner.Row) ([]string, []spanner.GenericColumnValue, error)
- func RowSeq(rows ...*spanner.Row) iter.Seq2[*spanner.Row, error]
- type DelimitedOption
- type DelimitedWriter
- func NewCSVWriter(out io.Writer, opts ...DelimitedOption) (*DelimitedWriter, error)
- func NewDelimitedWriter(out io.Writer, delimiter rune, options ...DelimitedOption) (*DelimitedWriter, error)
- func NewDelimitedWriterWithOptions(out io.Writer, delimiter rune, options ...DelimitedOption) (*DelimitedWriter, error)deprecated
- func (w *DelimitedWriter) Flush() error
- func (w *DelimitedWriter) FormatConfig() *spanvalue.FormatConfig
- func (w *DelimitedWriter) Prepare(metadata *sppb.ResultSetMetadata) errordeprecated
- func (w *DelimitedWriter) PrepareColumnNames(names []string) error
- func (w *DelimitedWriter) PrepareRowType(rowType *sppb.StructType) error
- func (w *DelimitedWriter) WriteGCVs(values []spanner.GenericColumnValue) error
- func (w *DelimitedWriter) WriteHeader() error
- func (w *DelimitedWriter) WriteRow(row *spanner.Row) error
- func (w *DelimitedWriter) WriteStructValues(values []*structpb.Value) error
- func (w *DelimitedWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
- type FlushWriter
- type Flusher
- type JSONLOption
- type JSONLWriter
- func (w *JSONLWriter) Flush() error
- func (w *JSONLWriter) FormatConfig() *spanvalue.FormatConfig
- func (w *JSONLWriter) Prepare(metadata *sppb.ResultSetMetadata) errordeprecated
- func (w *JSONLWriter) PrepareColumnNames(names []string) error
- func (w *JSONLWriter) PrepareRowType(rowType *sppb.StructType) error
- func (w *JSONLWriter) WriteGCVs(values []spanner.GenericColumnValue) error
- func (w *JSONLWriter) WriteRow(row *spanner.Row) error
- func (w *JSONLWriter) WriteStructValues(values []*structpb.Value) error
- func (w *JSONLWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
- type NameOption
- type Option
- type RowIteratorHooks
- func AfterEachSuccessfulWriteRow(base RowIteratorHooks, after func() error) RowIteratorHooks
- func NewRowIteratorHooks() RowIteratorHooks
- func ObserveWriteRow(base RowIteratorHooks, observe func(rowNum int) error) RowIteratorHooks
- func RowIteratorHooksFromWriter(w RowIteratorWriter) RowIteratorHooks
- func WithRowOrdinal(base RowIteratorHooks, ord *RowOrdinal) RowIteratorHooks
- func (h RowIteratorHooks) MarkOmitRowsRead() RowIteratorHooks
- func (h RowIteratorHooks) OnRunStart(fn func()) RowIteratorHooks
- func (h RowIteratorHooks) WithFinish(fn func(*RowIteratorResult) error) RowIteratorHooks
- func (h RowIteratorHooks) WithPrepareMetadata(fn func(*sppb.ResultSetMetadata) error) RowIteratorHooks
- func (h RowIteratorHooks) WithWriteRow(fn func(*spanner.Row) error) RowIteratorHooks
- type RowIteratorResult
- func RunRowIterator(iter *spanner.RowIterator, hooks RowIteratorHooks) (*RowIteratorResult, error)
- func RunRowSeq(md *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], ...) (*RowIteratorResult, error)
- func RunRowSeqDeferredMetadata(metadata func() *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], ...) (*RowIteratorResult, error)
- func WriteRowIterator(iter *spanner.RowIterator, w RowIteratorWriter) (*RowIteratorResult, error)
- func WriteRowSeq(md *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], ...) (*RowIteratorResult, error)
- type RowIteratorStats
- type RowIteratorWriter
- type RowOrdinal
- type SQLInsertKind
- type SQLInsertOption
- type SQLInsertWriter
- func (w *SQLInsertWriter) Flush() error
- func (w *SQLInsertWriter) FormatConfig() *spanvalue.FormatConfig
- func (w *SQLInsertWriter) Prepare(metadata *sppb.ResultSetMetadata) errordeprecated
- func (w *SQLInsertWriter) PrepareColumnNames(names []string) error
- func (w *SQLInsertWriter) PrepareRowType(rowType *sppb.StructType) error
- func (w *SQLInsertWriter) TableName() string
- func (w *SQLInsertWriter) WriteGCVs(values []spanner.GenericColumnValue) error
- func (w *SQLInsertWriter) WriteRow(row *spanner.Row) error
- func (w *SQLInsertWriter) WriteStructValues(values []*structpb.Value) error
- func (w *SQLInsertWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
- type Writer
Examples ¶
Constants ¶
const ( // Comma is the standard CSV field delimiter. Pass Comma to // NewDelimitedWriter for CSV output. Comma rune = ',' )
Variables ¶
var ( // ErrEmptyTableName reports that the SQL INSERT writer table name is empty. ErrEmptyTableName = errors.New("empty table name") // ErrEmptyColumnName reports that a SQL writer received an empty column name. ErrEmptyColumnName = errors.New("empty column name") // ErrNilOutputWriter reports that a writer was constructed without an output. ErrNilOutputWriter = errors.New("nil output writer") // ErrNilRow reports that WriteRow was called with a nil row, or that a // row sequence yielded a nil row with a nil error (see [RunRowSeq]). ErrNilRow = spanvalue.ErrNilRow // ErrMissingColumnNames reports that an operation requires a registered column schema // when none was registered yet, or column names/types are insufficient for the write // (for example values without names). It is not returned for a registered zero-column // schema (see package doc "Registered schema vs missing schema"). [*DelimitedWriter.PrepareColumnNames] // and [WithColumnNames] with an empty name list return this error; use [*DelimitedWriter.PrepareRowType] // or [WithRowType] for zero-column result sets. ErrMissingColumnNames = errors.New("missing column names") // ErrColumnNamesMismatch reports that provided column names differ from initialized schema. ErrColumnNamesMismatch = errors.New("column names mismatch") // ErrHeaderAfterData reports that DelimitedWriter.WriteHeader was called after data rows were emitted. ErrHeaderAfterData = errors.New("header after data") // ErrInvalidDelimiter reports that DelimitedWriter received an invalid delimiter. ErrInvalidDelimiter = errors.New("invalid delimiter") // ErrMissingFieldTypes reports that WriteStructValues requires registered field types. ErrMissingFieldTypes = errors.New("missing field types schema") // ErrMismatchedStructValueCount reports that WriteStructValues value count does not match the schema. ErrMismatchedStructValueCount = errors.New("mismatched struct value count") // ErrInvalidSQLInsertKindForDialect reports that [WithSQLInsertKind] selected INSERT OR IGNORE // or INSERT OR UPDATE with a PostgreSQL dialect. Those prefixes are GoogleSQL-only; use plain // [SQLInsert] with [WithSQLDialect](databasepb.DatabaseDialect_POSTGRESQL) instead. ErrInvalidSQLInsertKindForDialect = errors.New("INSERT OR IGNORE/UPDATE not supported for PostgreSQL dialect") // ErrInvalidSQLInsertKind reports that [WithSQLInsertKind] received a [SQLInsertKind] // outside the defined constants ([SQLInsert], [SQLInsertOrIgnore], [SQLInsertOrUpdate]). // [NewSQLInsertWriter] rejects such kinds at construction. ErrInvalidSQLInsertKind = errors.New("invalid SQLInsertKind") // ErrTableNameChangedMidBatch reports that the SQL INSERT table name was mutated while // a multi-row INSERT batch was open. ErrTableNameChangedMidBatch = errors.New("table name changed mid-batch") )
var ErrNilRowIterator = errors.New("nil row iterator")
ErrNilRowIterator reports that RunRowIterator or WriteRowIterator was called with a nil iterator.
var ErrNilRowSeq = errors.New("nil row sequence")
ErrNilRowSeq reports that RunRowSeq or WriteRowSeq was called with a nil row sequence.
var ErrNilWriter = errors.New("nil row iterator writer")
ErrNilWriter reports that WriteRowIterator was called with a nil writer.
Functions ¶
func FormatDelimitedRow ¶ added in v0.3.2
func FormatDelimitedRow(fc *spanvalue.FormatConfig, row *spanner.Row, delimiter rune) (string, error)
FormatDelimitedRow formats one row as a CSV-style delimited record without a trailing newline. Pass Comma for CSV output.
func FormatDelimitedValues ¶ added in v0.3.2
func FormatDelimitedValues(fc *spanvalue.FormatConfig, columnNames []string, values []spanner.GenericColumnValue, delimiter rune) (string, error)
FormatDelimitedValues formats one row represented as column names plus GCV values as a CSV-style delimited record without a trailing newline. Pass Comma for CSV output.
func FormatJSONLRow ¶ added in v0.3.2
func FormatJSONLRow(fc *spanvalue.FormatConfig, row *spanner.Row, namer spanvalue.UnnamedFieldNamer) (string, error)
FormatJSONLRow formats one row as a JSON object string without a trailing newline. Callers writing JSONL streams should add the newline at the stream boundary.
func FormatJSONLValues ¶ added in v0.3.2
func FormatJSONLValues(fc *spanvalue.FormatConfig, columnNames []string, values []spanner.GenericColumnValue, namer spanvalue.UnnamedFieldNamer) (string, error)
FormatJSONLValues formats one row represented as column names plus GCV values as a JSON object string without a trailing newline. Callers writing JSONL streams should add the newline at the stream boundary.
func RowSeq ¶ added in v0.7.4
RowSeq adapts already-built rows to the fallible sequence shape consumed by RunRowSeq and WriteRowSeq. Every pair it yields has a nil error; a nil row in rows is rejected by the consumer with ErrNilRow.
Row sources that can fail per row (for example lazy encoders) should produce their own iter.Seq2 instead of pre-building a slice for RowSeq.
Types ¶
type DelimitedOption ¶ added in v0.3.2
type DelimitedOption interface {
// contains filtered or unexported methods
}
DelimitedOption configures a DelimitedWriter created by NewDelimitedWriter or NewCSVWriter.
func DelimitedGCVExportOptions ¶ added in v0.5.0
func DelimitedGCVExportOptions( metadata *sppb.ResultSetMetadata, formatter *spanvalue.FormatConfig, namer spanvalue.UnnamedFieldNamer, ) []DelimitedOption
DelimitedGCVExportOptions returns WithMetadata, WithFormatter, and WithUnnamedFieldNamer for delimited writers that stream DelimitedWriter.WriteGCVs. Nil arguments are omitted so defaults are not overwritten accidentally.
func WithFlushEachRow ¶ added in v0.6.0
func WithFlushEachRow() DelimitedOption
WithFlushEachRow configures DelimitedWriter to flush the underlying encoding/csv buffer after each successful data row. Use for interactive streaming when consumers should see output before the export finishes; the default buffers until Flusher.Flush.
func WithHeader ¶ added in v0.3.2
func WithHeader(header bool) DelimitedOption
WithHeader sets whether DelimitedWriter emits a CSV/TSV header (default true). The header is written before the first data row, or on DelimitedWriter.Flush if only names were registered. See DelimitedWriter.WriteHeader to emit it earlier. Header emission is constructor-only configuration.
type DelimitedWriter ¶ added in v0.3.2
type DelimitedWriter struct {
// contains filtered or unexported fields
}
DelimitedWriter writes rows as CSV-style delimited text. By default, call Flush after the final write; WithFlushEachRow flushes encoding/csv after each data row instead. WithHeader controls automatic header output; see also DelimitedWriter.WriteHeader. Configuration is constructor-only (NewDelimitedWriter / NewCSVWriter options). After the first output write failure, every later Write*/Flush call returns that error; discard the writer (see package doc "Write errors").
func NewCSVWriter ¶
func NewCSVWriter(out io.Writer, opts ...DelimitedOption) (*DelimitedWriter, error)
NewCSVWriter returns a comma-delimited CSV writer configured by options. It is a thin helper for NewDelimitedWriter(out, Comma, opts...).
func NewDelimitedWriter ¶ added in v0.3.1
func NewDelimitedWriter(out io.Writer, delimiter rune, options ...DelimitedOption) (*DelimitedWriter, error)
NewDelimitedWriter returns a CSV-style writer using delimiter as the field delimiter and configured by options.
It supports CSV (delimiter Comma), quoted TSV (delimiter '\t'), or other single-rune delimiters. Output follows encoding/csv quoting rules, not raw field joins. Delimiter must be non-zero and a valid encoding/csv delimiter. See the package-level section "Quoted delimited text vs raw tab-separated".
func NewDelimitedWriterWithOptions
deprecated
added in
v0.3.2
func NewDelimitedWriterWithOptions(out io.Writer, delimiter rune, options ...DelimitedOption) (*DelimitedWriter, error)
NewDelimitedWriterWithOptions forwards to NewDelimitedWriter.
Deprecated: Use NewDelimitedWriter instead.
func (*DelimitedWriter) Flush ¶ added in v0.3.2
func (w *DelimitedWriter) Flush() error
Flush flushes buffered delimited data to the underlying writer. When the header is enabled (WithHeader, default true), a schema is registered, len(column names) > 0, and no header was written yet, Flush writes the header first (this covers zero-row SELECT exports; with data rows present the header was already written by the first row). With a registered zero-column schema, Flush succeeds without writing. With no registered schema, no written data, and the header enabled, Flush returns ErrMissingColumnNames. After a write failure, Flush returns the latched error without flushing (see package doc "Write errors"). Flush does not close the underlying writer.
func (*DelimitedWriter) FormatConfig ¶ added in v0.5.0
func (w *DelimitedWriter) FormatConfig() *spanvalue.FormatConfig
FormatConfig returns the effective formatter used for delimited value cells. When no formatter is configured, this returns spanvalue.SimpleFormatConfig. Configure it only via NewDelimitedWriter, NewCSVWriter, or WithFormatter.
func (*DelimitedWriter) Prepare
deprecated
added in
v0.3.2
func (w *DelimitedWriter) Prepare(metadata *sppb.ResultSetMetadata) error
Prepare initializes the delimited schema from result-set metadata before the first row is written.
Deprecated: Use DelimitedWriter.PrepareRowType or DelimitedWriter.PrepareColumnNames.
func (*DelimitedWriter) PrepareColumnNames ¶ added in v0.4.1
func (w *DelimitedWriter) PrepareColumnNames(names []string) error
PrepareColumnNames registers column names only; same as WithColumnNames for non-empty names. Unlike WithColumnNames, an empty names slice returns ErrMissingColumnNames; for zero-column result sets use DelimitedWriter.PrepareRowType instead.
func (*DelimitedWriter) PrepareRowType ¶ added in v0.4.1
func (w *DelimitedWriter) PrepareRowType(rowType *sppb.StructType) error
PrepareRowType registers column names and field types; same as WithRowType. Nil rowType and a row type with no fields both register an empty schema (see package doc).
func (*DelimitedWriter) WriteGCVs ¶ added in v0.3.2
func (w *DelimitedWriter) WriteGCVs(values []spanner.GenericColumnValue) error
WriteGCVs writes one row from GCVs; see package doc "Column names and field types".
func (*DelimitedWriter) WriteHeader ¶ added in v0.3.2
func (w *DelimitedWriter) WriteHeader() error
WriteHeader writes the CSV/TSV header once; a column schema must already be registered. With zero registered column names (empty row type), WriteHeader succeeds without writing. With no registered schema, it returns ErrMissingColumnNames; after a data row was written with the header disabled (WithHeader(false)), it returns ErrHeaderAfterData. When the header is enabled (the default), DelimitedWriter.Flush also writes a pending header.
func (*DelimitedWriter) WriteRow ¶ added in v0.3.2
func (w *DelimitedWriter) WriteRow(row *spanner.Row) error
WriteRow writes one delimited row; see package doc "Column names and field types".
func (*DelimitedWriter) WriteStructValues ¶ added in v0.4.1
func (w *DelimitedWriter) WriteStructValues(values []*structpb.Value) error
WriteStructValues writes one row from []*structpb.Value; see package doc "Column names and field types".
func (*DelimitedWriter) WriteValues ¶ added in v0.3.2
func (w *DelimitedWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
WriteValues writes one row from column names and GCVs.
type FlushWriter ¶ added in v0.3.2
FlushWriter streams Spanner rows and finalizes any buffered output.
type Flusher ¶ added in v0.3.1
type Flusher interface {
Flush() error
}
Flusher finalizes any buffered output. Flush does not close the underlying io.Writer. DelimitedWriter uses Flush to forward buffered CSV-style data; JSONLWriter implements Flush as a no-op. SQLInsertWriter.Flush closes a partial multi-row INSERT when WithSQLBatchSize is greater than 1.
type JSONLOption ¶ added in v0.3.2
type JSONLOption interface {
// contains filtered or unexported methods
}
JSONLOption configures a JSONLWriter created by NewJSONLWriter.
func JSONLGCVExportOptions ¶ added in v0.5.0
func JSONLGCVExportOptions( metadata *sppb.ResultSetMetadata, formatter *spanvalue.FormatConfig, namer spanvalue.UnnamedFieldNamer, ) []JSONLOption
JSONLGCVExportOptions returns the same trio for NewJSONLWriter and JSONLWriter.WriteGCVs. Nil arguments are omitted so defaults are not overwritten accidentally.
type JSONLWriter ¶
type JSONLWriter struct {
// contains filtered or unexported fields
}
JSONLWriter streams one JSON object per line using github.com/apstndb/spanvalue JSON formatting. After the first output write failure, every later Write*/Flush call returns that error; discard the writer (see package doc "Write errors").
func NewJSONLWriter ¶
func NewJSONLWriter(out io.Writer, options ...JSONLOption) (*JSONLWriter, error)
NewJSONLWriter returns a JSONL writer configured by options.
func NewJSONLWriterWithOptions
deprecated
added in
v0.3.2
func NewJSONLWriterWithOptions(out io.Writer, options ...JSONLOption) (*JSONLWriter, error)
NewJSONLWriterWithOptions forwards to NewJSONLWriter.
Deprecated: Use NewJSONLWriter instead.
func (*JSONLWriter) Flush ¶ added in v0.3.1
func (w *JSONLWriter) Flush() error
Flush finalizes JSONL output. JSONLWriter is unbuffered, so this writes nothing; after a write failure it returns the latched error (see package doc "Write errors").
func (*JSONLWriter) FormatConfig ¶ added in v0.5.0
func (w *JSONLWriter) FormatConfig() *spanvalue.FormatConfig
FormatConfig returns the effective formatter used for JSONL value encoding. When no formatter is configured, this returns spanvalue.JSONFormatConfig. Configure it only via NewJSONLWriter or WithFormatter.
func (*JSONLWriter) Prepare
deprecated
added in
v0.3.2
func (w *JSONLWriter) Prepare(metadata *sppb.ResultSetMetadata) error
Prepare initializes the JSONL schema from result-set metadata before the first row is written. If a schema is already initialized, Prepare verifies that the metadata column names match the existing schema.
Deprecated: Use JSONLWriter.PrepareRowType, JSONLWriter.PrepareColumnNames, WithRowType, WithColumnNames, or WithMetadata.
func (*JSONLWriter) PrepareColumnNames ¶ added in v0.4.1
func (w *JSONLWriter) PrepareColumnNames(names []string) error
PrepareColumnNames registers column names; see DelimitedWriter.PrepareColumnNames.
func (*JSONLWriter) PrepareRowType ¶ added in v0.4.1
func (w *JSONLWriter) PrepareRowType(rowType *sppb.StructType) error
PrepareRowType registers names and field types; see DelimitedWriter.PrepareRowType. Nil rowType registers an empty schema.
func (*JSONLWriter) WriteGCVs ¶
func (w *JSONLWriter) WriteGCVs(values []spanner.GenericColumnValue) error
WriteGCVs writes one row; see DelimitedWriter.WriteGCVs.
func (*JSONLWriter) WriteRow ¶
func (w *JSONLWriter) WriteRow(row *spanner.Row) error
WriteRow writes one JSONL row. Does not require With* or Prepare*; see DelimitedWriter.WriteRow.
func (*JSONLWriter) WriteStructValues ¶ added in v0.4.1
func (w *JSONLWriter) WriteStructValues(values []*structpb.Value) error
WriteStructValues writes one row; see DelimitedWriter.WriteStructValues.
func (*JSONLWriter) WriteValues ¶
func (w *JSONLWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
WriteValues writes one row; see DelimitedWriter.WriteValues.
type NameOption ¶ added in v0.3.2
type NameOption interface {
DelimitedOption
JSONLOption
}
NameOption configures field-name handling for delimited and JSONL writers.
func WithUnnamedFieldNamer ¶ added in v0.3.2
func WithUnnamedFieldNamer(namer spanvalue.UnnamedFieldNamer) NameOption
WithUnnamedFieldNamer sets the unnamed-field naming policy for delimited and JSONL writers. The same namer must be passed to spanvalue.ColumnNames when resolving display headers outside the writer (for example CLI table output alongside CSV export).
type Option ¶ added in v0.3.2
type Option interface {
DelimitedOption
JSONLOption
SQLInsertOption
}
Option configures any writer type created by a writer constructor.
func WithColumnNames ¶ added in v0.4.1
WithColumnNames registers column names only and clears any registered field types. An empty names slice returns ErrMissingColumnNames; use WithRowType for a zero-column result set.
func WithFormatter ¶ added in v0.3.2
func WithFormatter(formatter *spanvalue.FormatConfig) Option
WithFormatter sets the FormatConfig used by a writer. A nil formatter selects the writer-type default: DelimitedWriter uses spanvalue.SimpleFormatConfig, JSONLWriter uses spanvalue.JSONFormatConfig, and SQLInsertWriter uses spanvalue.LiteralFormatConfig. Writers do not call *spanvalue.FormatConfig.Validate on the supplied config; validate hand-built formatters before construction when early failure is desired.
func WithMetadata ¶ added in v0.3.2
func WithMetadata(metadata *sppb.ResultSetMetadata) Option
WithMetadata registers names and types from metadata.GetRowType(); same as WithRowType. Other metadata fields are ignored.
When printing table headers or other column labels outside the writer, use spanvalue.ColumnNames with the same spanvalue.UnnamedFieldNamer as WithUnnamedFieldNamer so export columns match displayed headers.
func WithRowType ¶ added in v0.4.1
func WithRowType(rowType *sppb.StructType) Option
WithRowType registers column names and field types at construction. Nil rowType registers an empty schema (see package doc).
type RowIteratorHooks ¶ added in v0.4.2
type RowIteratorHooks struct {
PrepareMetadata func(*sppb.ResultSetMetadata) error
WriteRow func(*spanner.Row) error
Finish func(*RowIteratorResult) error
// contains filtered or unexported fields
}
RowIteratorHooks drives RunRowIterator. Nil function fields are skipped.
RowIteratorHooks contains unexported fields, so other packages cannot use unkeyed composite literals. Construct hooks with NewRowIteratorHooks, RowIteratorHooksFromWriter, or package decorators (WithRowOrdinal, ObserveWriteRow, AfterEachSuccessfulWriteRow); set exported callbacks with RowIteratorHooks.WithPrepareMetadata, RowIteratorHooks.WithWriteRow, and RowIteratorHooks.WithFinish, keyed composite literals, or by assigning exported fields on a value from NewRowIteratorHooks. Customize run behavior with RowIteratorHooks.MarkOmitRowsRead and RowIteratorHooks.OnRunStart.
PrepareMetadata runs once after the first spanner.RowIterator.Next, with whatever cloud.google.com/go/spanner.RowIterator.Metadata holds at that point (including nil for DML or stats-only iterators, and including when the only result is iterator.Done). It is not called when the first Next returns a query error other than iterator.Done.
Finish runs only after all rows are consumed without error. If PrepareMetadata or WriteRow returns an error, the loop aborts and Finish is not called. The returned RowIteratorResult is still populated with whatever iter.Metadata and stats are available at the abort point. Stats is fully populated when Finish runs successfully. QueryPlan and QueryStats require QueryWithStats. Finish may read Metadata again for end-of-stream processing.
func AfterEachSuccessfulWriteRow ¶ added in v0.5.0
func AfterEachSuccessfulWriteRow(base RowIteratorHooks, after func() error) RowIteratorHooks
AfterEachSuccessfulWriteRow wraps base.WriteRow to call after on each successful delegation. after is typically a buffered I/O flush for interactive streaming; do not pass SQLInsertWriter.Flush here because that finalizes an open INSERT batch and disables batching. When base.WriteRow is nil, after still runs once per streamed row.
func NewRowIteratorHooks ¶ added in v0.5.0
func NewRowIteratorHooks() RowIteratorHooks
NewRowIteratorHooks returns an empty hooks value for RowIteratorHooksFromWriter, package decorators, or custom decoration.
func ObserveWriteRow ¶ added in v0.5.0
func ObserveWriteRow(base RowIteratorHooks, observe func(rowNum int) error) RowIteratorHooks
ObserveWriteRow wraps base.WriteRow to call observe with the 1-based row index before delegating. Returning an error from observe aborts without calling base.WriteRow. When base.WriteRow is nil, observe still runs for each row.
func RowIteratorHooksFromWriter ¶ added in v0.4.2
func RowIteratorHooksFromWriter(w RowIteratorWriter) RowIteratorHooks
RowIteratorHooksFromWriter returns hooks that register metadata via RowIteratorWriter.PrepareRowType, write each row, and call Flusher.Flush in Finish. Flush is not called when PrepareRowType or WriteRow returns an error. A nil writer returns empty hooks.
func WithRowOrdinal ¶ added in v0.5.0
func WithRowOrdinal(base RowIteratorHooks, ord *RowOrdinal) RowIteratorHooks
WithRowOrdinal wraps base so ord.Current is set to the 1-based row index immediately before each WriteRow attempt on base. ord.Current resets to 0 at the start of each RunRowIterator call (including when the first Next returns a query error before PrepareMetadata). A nil ord is ignored and base is returned as-is. When base.WriteRow is nil, the wrapper still runs so ord is updated for each streamed row.
func (RowIteratorHooks) MarkOmitRowsRead ¶ added in v0.5.0
func (h RowIteratorHooks) MarkOmitRowsRead() RowIteratorHooks
MarkOmitRowsRead configures the hooks so successful WriteRow calls do not increment RowIteratorResult.RowsRead. Use when WriteRow exists only for side effects (for example a custom decorator) and should not count as exported rows.
func (RowIteratorHooks) OnRunStart ¶ added in v0.5.0
func (h RowIteratorHooks) OnRunStart(fn func()) RowIteratorHooks
OnRunStart registers fn to run once at the beginning of each RunRowIterator call. Multiple calls chain in registration order. A nil fn is ignored.
func (RowIteratorHooks) WithFinish ¶ added in v0.5.0
func (h RowIteratorHooks) WithFinish(fn func(*RowIteratorResult) error) RowIteratorHooks
WithFinish sets Finish and returns h.
func (RowIteratorHooks) WithPrepareMetadata ¶ added in v0.5.0
func (h RowIteratorHooks) WithPrepareMetadata(fn func(*sppb.ResultSetMetadata) error) RowIteratorHooks
WithPrepareMetadata sets PrepareMetadata and returns h.
func (RowIteratorHooks) WithWriteRow ¶ added in v0.5.0
func (h RowIteratorHooks) WithWriteRow(fn func(*spanner.Row) error) RowIteratorHooks
WithWriteRow sets WriteRow and returns h.
type RowIteratorResult ¶ added in v0.4.2
type RowIteratorResult struct {
Metadata *sppb.ResultSetMetadata
Stats RowIteratorStats
RowsRead int
}
RowIteratorResult is the metadata and stats available from a cloud.google.com/go/spanner.RowIterator after RunRowIterator returns.
Application code should use the value returned by RunRowIterator or WriteRowIterator rather than constructing RowIteratorResult manually. When a value must be built outside this package (for example in tests), use keyed composite literals so added exported fields do not break positional literals. On the error path, stats fields reflect whatever the iterator had populated at the abort point and may be zero until iterator.Done (QueryStats and RowCount are only fully populated after a successful run).
RowsRead counts data rows for which hooks.WriteRow returned nil during the run, including on the error path when iteration aborts mid-stream. It stays zero when WriteRow is nil (rows may still be consumed from the iterator). Hook decorators that install a WriteRow wrapper only for ordinal or observe side effects do not increment RowsRead unless the wrapped hooks already had WriteRow set. RowsRead is distinct from RowIteratorStats.RowCount, which follows Spanner iterator semantics (DML row count after iterator.Done).
func RunRowIterator ¶ added in v0.4.2
func RunRowIterator(iter *spanner.RowIterator, hooks RowIteratorHooks) (*RowIteratorResult, error)
RunRowIterator streams all rows from iter using hooks. The helper owns iter: it consumes the iterator, always calls *cloud.google.com/go/spanner.RowIterator.Stop on return, and returns metadata and stats through RowIteratorResult.
Prefer passing a newly created iterator directly, without binding it at the call site:
result, err := RunRowIterator(txn.Query(ctx, stmt), hooks)
Do not call *cloud.google.com/go/spanner.RowIterator.Stop on iter after RunRowIterator returns; the helper already stopped it. Use the returned RowIteratorResult for metadata, query stats, and row counts—not iter.Metadata, iter.QueryStats, iter.RowCount, or iter.QueryPlan after the call. Reading those fields after Stop does not panic in current cloud.google.com/go/spanner releases, but the iterator is transferred and the result value is the supported post-run API.
The returned RowIteratorResult reflects iter.Metadata and iter stats fields after Stop and the loop (including when no data rows were written). When hooks.Finish is set, it receives the same pointer that is returned.
func RunRowSeq ¶ added in v0.7.4
func RunRowSeq(md *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], hooks RowIteratorHooks) (*RowIteratorResult, error)
RunRowSeq drives hooks over rows that do not come from a cloud.google.com/go/spanner.RowIterator — client-side (virtual) result sets, locally constructed rows, or lazily encoded rows. It shares the hook contract of RunRowIterator: PrepareMetadata runs once with md before the first data row (including when the sequence is empty), WriteRow runs per row, a failure aborts the run without calling Finish, and Finish runs only after all rows succeed. RowIteratorResult.RowsRead counts successful WriteRow calls.
md is passed to PrepareMetadata as-is; nil is allowed and mirrors a metadata-less iterator, but writer-backed hooks generally need row-type metadata to emit headers. RowIteratorStats in the result is always zero: there is no Spanner iterator to report stats.
A non-nil error yielded by rows aborts the run and is returned; the row paired with it is ignored and the sequence is not consumed further. A nil row yielded with a nil error aborts the run with ErrNilRow.
func RunRowSeqDeferredMetadata ¶ added in v0.7.6
func RunRowSeqDeferredMetadata(metadata func() *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], hooks RowIteratorHooks) (*RowIteratorResult, error)
RunRowSeqDeferredMetadata is RunRowSeq for producers that learn the row type only after producing begins — merged concurrent sources, lazily decoded streams. metadata is called lazily on the RunRowIterator schedule: once immediately before PrepareMetadata, which runs after the first pair has been pulled from rows (or after rows ends when it is empty), and again when assembling RowIteratorResult. A producer therefore only has to publish the row type before yielding its first pair, with no need to hold rows back; a nil metadata func is treated as always-nil metadata.
All other semantics match RunRowSeq.
func WriteRowIterator ¶ added in v0.4.2
func WriteRowIterator(iter *spanner.RowIterator, w RowIteratorWriter) (*RowIteratorResult, error)
WriteRowIterator streams all rows from iter into w using RowIteratorHooksFromWriter. See RunRowIterator for iterator ownership, metadata, stats, and zero-row behavior.
Prefer passing a newly created iterator directly:
result, err := WriteRowIterator(txn.Query(ctx, stmt), w)
When an application drives cloud.google.com/go/spanner.RowIterator.Next directly (instead of WriteRowIterator or RunRowIterator), bind the iterator, call *cloud.google.com/go/spanner.RowIterator.Stop on return (typically defer iter.Stop()), and read metadata or stats fields only after consuming rows to iterator.Done.
When an application skips row bodies but still needs a header-only delimited finish, call RowIteratorWriter.PrepareRowType with iter.Metadata.GetRowType() after the Next loop, then Flusher.Flush; see package README on GitHub (writer/README.md).
func WriteRowSeq ¶ added in v0.7.4
func WriteRowSeq(md *sppb.ResultSetMetadata, rows iter.Seq2[*spanner.Row, error], w RowIteratorWriter) (*RowIteratorResult, error)
WriteRowSeq streams rows into w using RowIteratorHooksFromWriter, the in-memory counterpart of WriteRowIterator. See RunRowSeq for the hook contract, metadata, and error semantics; like WriteRowIterator, an empty sequence still registers metadata and flushes, so delimited writers emit a header-only result.
Example ¶
ExampleWriteRowSeq streams a client-side (virtual) result set — rows that do not come from a *spanner.RowIterator — through a CSV writer, with explicit metadata supplying the header.
package main
import (
"fmt"
"os"
"cloud.google.com/go/spanner"
sppb "cloud.google.com/go/spanner/apiv1/spannerpb"
"github.com/apstndb/spanvalue/writer"
)
func main() {
names := []string{"name", "value"}
row1, err := spanner.NewRow(names, []any{"AUTOCOMMIT", "TRUE"})
if err != nil {
fmt.Println(err)
return
}
row2, err := spanner.NewRow(names, []any{"READONLY", "FALSE"})
if err != nil {
fmt.Println(err)
return
}
md := &sppb.ResultSetMetadata{RowType: &sppb.StructType{Fields: []*sppb.StructType_Field{
{Name: "name", Type: &sppb.Type{Code: sppb.TypeCode_STRING}},
{Name: "value", Type: &sppb.Type{Code: sppb.TypeCode_STRING}},
}}}
w, err := writer.NewCSVWriter(os.Stdout)
if err != nil {
fmt.Println(err)
return
}
if _, err := writer.WriteRowSeq(md, writer.RowSeq(row1, row2), w); err != nil {
fmt.Println(err)
return
}
}
Output: name,value AUTOCOMMIT,TRUE READONLY,FALSE
type RowIteratorStats ¶ added in v0.4.2
RowIteratorStats holds execution information populated on a cloud.google.com/go/spanner.RowIterator after iteration completes. QueryPlan and QueryStats are set when the query used QueryWithStats. RowCount is set for DML after iterator.Done.
type RowIteratorWriter ¶ added in v0.4.2
type RowIteratorWriter interface {
FlushWriter
PrepareRowType(*sppb.StructType) error
}
RowIteratorWriter streams rows from a cloud.google.com/go/spanner.RowIterator through WriteRowIterator. DelimitedWriter, JSONLWriter, and SQLInsertWriter implement it.
type RowOrdinal ¶ added in v0.5.0
type RowOrdinal struct {
// Current is the 1-based index of the row about to be written, or the last
// row whose WriteRow completed without error. It remains 0 until the first
// data row is reached. On WriteRow error, Current is the failing row index.
Current int
}
RowOrdinal holds a 1-based row index for diagnostics while streaming rows. Use WithRowOrdinal to keep it updated around WriteRow.
type SQLInsertKind ¶ added in v0.4.0
type SQLInsertKind int
SQLInsertKind selects the INSERT statement prefix written by SQLInsertWriter.
Variants follow Spanner GoogleSQL DML: INSERT OR IGNORE skips rows whose primary key already exists; INSERT OR UPDATE inserts or updates by primary key. They cannot be combined with ON CONFLICT in the same statement, and INSERT is not supported in Partitioned DML. See https://cloud.google.com/spanner/docs/reference/standard-sql/dml-syntax .
SQLInsertOrIgnore and SQLInsertOrUpdate are invalid with WithSQLDialect(databasepb.DatabaseDialect_POSTGRESQL); NewSQLInsertWriter rejects that combination at construction via ErrInvalidSQLInsertKindForDialect.
const ( // SQLInsert writes plain INSERT INTO statements. SQLInsert SQLInsertKind = iota // SQLInsertOrIgnore writes INSERT OR IGNORE INTO statements. SQLInsertOrIgnore // SQLInsertOrUpdate writes INSERT OR UPDATE INTO statements. SQLInsertOrUpdate )
func (SQLInsertKind) String ¶ added in v0.4.0
func (k SQLInsertKind) String() string
type SQLInsertOption ¶ added in v0.3.2
type SQLInsertOption interface {
// contains filtered or unexported methods
}
SQLInsertOption configures a SQLInsertWriter created by NewSQLInsertWriter.
func WithSQLBatchSize ¶ added in v0.5.0
func WithSQLBatchSize(n int) SQLInsertOption
WithSQLBatchSize sets how many rows SQLInsertWriter combines into one INSERT statement. Values 0 or 1 keep the default of one row per statement. Values greater than 1 emit multi-row INSERT ... VALUES (...), (...); up to n rows per statement. Rows of a multi-row statement are buffered in memory and the completed statement is emitted with a single Write, so an I/O failure never leaves a partially written tuple. Call SQLInsertWriter.Flush after the final row to close a partial batch (Flush is also safe when the last batch closed exactly on a size boundary).
Batching applies the same SQLInsertKind prefix once per batched statement. Multi-row INSERT OR IGNORE and INSERT OR UPDATE follow Spanner GoogleSQL DML rules and require GoogleSQL dialect; PostgreSQL rejects those prefixes (ErrInvalidSQLInsertKindForDialect). Identifier quoting follows WithSQLDialect; value literals still use WithFormatter.
func WithSQLDialect ¶ added in v0.5.0
func WithSQLDialect(dialect databasepb.DatabaseDialect) SQLInsertOption
WithSQLDialect sets identifier quoting for table and column names in SQL INSERT output. It does not change INSERT statement prefixes (WithSQLInsertKind) or value literal formatting (WithFormatter). The default is GoogleSQL (databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL).
PostgreSQL dialect does not support SQLInsertOrIgnore or SQLInsertOrUpdate prefixes; combining them returns ErrInvalidSQLInsertKindForDialect at construction.
func WithSQLInsertKind ¶ added in v0.4.0
func WithSQLInsertKind(kind SQLInsertKind) SQLInsertOption
WithSQLInsertKind sets the INSERT statement variant for a SQLInsertWriter. Kinds outside the defined constants are rejected by NewSQLInsertWriter with ErrInvalidSQLInsertKind.
type SQLInsertWriter ¶
type SQLInsertWriter struct {
// contains filtered or unexported fields
}
SQLInsertWriter streams INSERT (or INSERT OR …) statements with dialect-aware identifier quoting for a fixed table. Each statement is built in memory and emitted with a single Write, so an I/O failure never leaves a partially written statement appended to by later calls. After any write error from *SQLInsertWriter.WriteRow, *SQLInsertWriter.WriteGCVs, *SQLInsertWriter.WriteValues, *SQLInsertWriter.WriteStructValues, or *SQLInsertWriter.Flush, every later Write*/Flush call returns that first error; discard the writer (see package doc "Write errors").
func NewSQLInsertWriter ¶
func NewSQLInsertWriter(out io.Writer, table string, options ...SQLInsertOption) (*SQLInsertWriter, error)
NewSQLInsertWriter returns a SQL INSERT writer configured by options. table must be non-empty after trimming whitespace (per strings.TrimSpace); otherwise NewSQLInsertWriter returns ErrEmptyTableName. Qualified names with empty segments (for example "db..users") are rejected at the first write via ErrEmptyTableName.
func NewSQLInsertWriterWithOptions
deprecated
added in
v0.3.2
func NewSQLInsertWriterWithOptions(out io.Writer, table string, options ...SQLInsertOption) (*SQLInsertWriter, error)
NewSQLInsertWriterWithOptions forwards to NewSQLInsertWriter.
Deprecated: Use NewSQLInsertWriter instead.
func (*SQLInsertWriter) Flush ¶ added in v0.3.1
func (w *SQLInsertWriter) Flush() error
Flush finalizes a partial multi-row INSERT batch started by WithSQLBatchSize. When batch size is 0 or 1, or when the last batch closed on a size boundary, Flush is a no-op. Flush is safe to call unconditionally after the final row: after a write failure it returns the latched error without writing (see package doc "Write errors").
func (*SQLInsertWriter) FormatConfig ¶ added in v0.5.0
func (w *SQLInsertWriter) FormatConfig() *spanvalue.FormatConfig
FormatConfig returns the effective formatter used for INSERT value literals. When no formatter is configured, this returns spanvalue.LiteralFormatConfig. Configure it only via NewSQLInsertWriter or WithFormatter.
func (*SQLInsertWriter) Prepare
deprecated
added in
v0.3.2
func (w *SQLInsertWriter) Prepare(metadata *sppb.ResultSetMetadata) error
Prepare initializes the SQL INSERT schema from result-set metadata before the first row is written. If a schema is already initialized, Prepare verifies that the metadata column names match the existing schema.
Deprecated: Use SQLInsertWriter.PrepareRowType, SQLInsertWriter.PrepareColumnNames, WithRowType, WithColumnNames, or WithMetadata.
func (*SQLInsertWriter) PrepareColumnNames ¶ added in v0.4.1
func (w *SQLInsertWriter) PrepareColumnNames(names []string) error
PrepareColumnNames initializes the SQL INSERT schema from column names before the first row is written. See DelimitedWriter.PrepareColumnNames for empty-name behavior.
func (*SQLInsertWriter) PrepareRowType ¶ added in v0.4.1
func (w *SQLInsertWriter) PrepareRowType(rowType *sppb.StructType) error
PrepareRowType initializes the SQL INSERT schema from a row type before the first row is written. When the row type comes from a cloud.google.com/go/spanner.RowIterator, use RunRowIterator or *SQLInsertWriter.PrepareRowType with iter.Metadata.GetRowType() after the first Next. Nil rowType registers an empty schema; *SQLInsertWriter.WriteGCVs still requires at least one column to emit SQL.
func (*SQLInsertWriter) TableName ¶ added in v0.5.0
func (w *SQLInsertWriter) TableName() string
TableName returns the qualified table name used in INSERT statements. Configure it only via NewSQLInsertWriter; create a new writer to use a different table.
func (*SQLInsertWriter) WriteGCVs ¶
func (w *SQLInsertWriter) WriteGCVs(values []spanner.GenericColumnValue) error
func (*SQLInsertWriter) WriteStructValues ¶ added in v0.4.1
func (w *SQLInsertWriter) WriteStructValues(values []*structpb.Value) error
WriteStructValues writes one row from structpb values using the field-type schema registered by WithRowType, WithMetadata, or *SQLInsertWriter.PrepareRowType.
func (*SQLInsertWriter) WriteValues ¶
func (w *SQLInsertWriter) WriteValues(columnNames []string, values []spanner.GenericColumnValue) error
type Writer ¶
Writer writes Spanner rows to an output stream.
WriteRow supplies column names and values on each call; see package doc "Column names and field types". Writer intentionally models row streaming only. Some concrete writers also implement Flusher; callers that own the full write lifecycle must call Flush after the final row when it is available. Factories that may return a buffered writer should return a concrete type or FlushWriter, not Writer alone.