Releases: apstndb/spanenc
Release list
v0.3.2
Final feature release of spanenc. Development moved to spancodec, which merges spanenc and spandec into a single codec module (see its v0.1.0 release and migration table). spanenc v0.3.x remains usable but frozen; the module carries a Deprecated: notice as of this release.
Added since v0.3.1
RowEncoder.MustResultSetMetadata(#3):ResultSetMetadatapanicking on error, for package-level wiring next toMustNewRowEncoder— row-type resolution is deterministic perTand column mask, and Must construction alone does not guarantee an inferable row type. Surveyed from apstndb/spanner-mycli#661.- Custom value encoders (#5 phase 1):
WithValueEncoder[T]registers a per-call encoder for Go types outside the client's coverage (uint32 and other integer width variants,time.Duration, external types that cannot implementspanner.Encoder), consulted before the client mirror; the newErrFallthroughdefers to the built-in encoding. Registrations apply per element of[]T;WithGoType[T]supplies the ARRAY element type for nil/empty slices and forValuesFromSlice/ArrayValueFromSlicestatic inference. Exact-type matching only; no package-global registry. Hazard (documented and pinned by test): registeringtime.Timebypasses thespanner.CommitTimestampsentinel.
#5 phase 2 (construction-time registrations informing RowEncoder.RowType, options on TypeFor/RowTypeFor) ships in spancodec v0.1.0, not here.
Requirements
github.com/apstndb/spanvaluev0.7.3+.cloud.google.com/go/spanner: v1.84.1 MVS floor; mirrored semantics track v1.91.0.
v0.3.1
Patch release since v0.3.0: additive RowEncoder APIs and internal adoption of spanvalue gcvctor constructors. No breaking changes. Mirrored semantics still track cloud.google.com/go/spanner v1.91.0.
Added
RowEncoder.Row(v, encodeOpts...)/RowEncoder.Rows(items, encodeOpts...)(#2): encode structs into real*spanner.Rowvalues so client-side (virtual) result sets flow through the same*spanner.Rowpipelines as server query results.RowisValues+spanner.NewRow(the client deep-clones the GCVs without re-encoding, so rows carry exactly the encoder's output including typed NULLs and do not alias encoder state).Rowsreturns a lazyiter.Seq2[*spanner.Row, error]— each row is encoded only when yielded (pinned by test), and an encode failure yields(nil, err)once and stops. Pairs withwriter.RunRowSeq/writer.WriteRowSeqintroduced in spanvalue v0.7.4, whose abort-on-yielded-error contract matches by design.MustNewRowEncoder[T]:NewRowEncoderpanicking on error, for package-level encoders of compile-time-known struct types — prefer it over local panic-on-error wrappers (surveyed from the spanner-mycli PoC, apstndb/spanner-mycli#657).
Changed (no behavior change)
- Hand-rolled nullable encoding helpers were replaced with the
gcvctorconstructors added for spanenc in spanvalue v0.7.2–v0.7.3 (apstndb/spanvalue#232, apstndb/spanvalue#236):IntervalFromNullable,NumericFromNullable(behind the existing NUMERIC validation),PGNumericFromNullable,JSONFromNullable,PGJSONBFromNullable. Client-mirror semantics are unchanged and pinned by tests, including the quoted-JSON-string wire for string-valuedNullJSON/PGJsonB(fixed on the spanvalue side in v0.7.3; spanenc deliberately skipped the v0.7.2 helpers whose string special-case diverged from the client).
Documentation
- Adoption guide: the string-only-row exception now spells out when routing display strings through GCVs is worth it (a single cell pipeline shared with server results, or scaffolding call sites that will gain typed columns) and asks adopters to record that intent in a comment; NULL cells should be detected with
spanvalue.IsNullrather than inspecting the protobuf value kind by hand.
Requirements
github.com/apstndb/spanvaluev0.7.3+ (gcvctorJSONFromNullable/PGJSONBFromNullablewith client-compatible marshal semantics). StreamingRowEncoder.Rowsoutput throughwriter.WriteRowSeqadditionally needs spanvalue v0.7.4+.github.com/apstndb/structfieldsv0.1.0 /structfields/spannertagv0.1.0 (unchanged).cloud.google.com/go/spanner: go.mod declares only the v1.84.1 MVS floor (mirrored semantics track v1.91.0); your module controls the actual client version.
Full changelog
v0.3.0
Stable release consolidating v0.3.0-alpha.1 and v0.3.0-alpha.2 plus documentation work. Mirrored semantics track cloud.google.com/go/spanner v1.91.0.
Added since v0.2.0
- Column masks for the plain-Go-value helpers (
ColumnMaskOption):WithColumns(...)(include) /WithoutColumns(...)(exclude) onMutationColumnsAndValues,MutationMap, andParamsMap. Masked output keeps struct declaration order; unknown columns, read-only columns in a write-shaped include list, or combining both kinds return the newErrInvalidColumnMask. WithLossOfPrecisionHandling(spanner.NumericError | spanner.NumericRound)(EncodeOption): per-call NUMERIC loss-of-precision control reusing the client's enum. The client's package-global is never read; the default here isspanner.NumericError(validate →ErrNumericOutOfRange), while the client's global defaults toNumericRound.ParamsMap: struct →map[string]anyforspanner.StatementParams (no GCV conversion; read-only fields included, since they are ordinary bindable values).ResultSetMetadataFor[T]/ResultSetMetadataFromGoType:RowTypeForwrapped in*sppb.ResultSetMetadatafor writer metadata and client-side virtual result sets.RowEncoder[T](NewRowEncoder): compiled row codec — field listing, column mask, and row type resolved once; per row onlyValues(v, encodeOpts...)runs.Columns()/RowType()/ResultSetMetadata()are mask-aware; agreement withStructColumnsAndValuesis pinned by tests.
Documentation
- Package-doc Adoption guide (readable from
go doc/ pkg.go.dev): when GCV-based rows pay off vs plain string rows;Columns()vsResultSetMetadata(); formatting stability across spanvalue upgrades; cross-links tospanvalue.FormatRowColumnsso adopters need no per-app GCV→string bridge. Driven by spanner-mycli PoC feedback (#1). ExampleStructColumns_tagOptionspins the tag-option quirk (row-shaped helpers parse;options; STRUCT-typed values keep the client's raw-tag behavior).- Corrected an earlier doc claim: the client's global loss-of-precision default is
NumericRound, notNumericError.
Design notes
Two decisions from review are deliberate API shape: option types stay split (EncodeOption vs ColumnMaskOption) so applicability is fixed at compile time, and ParamsMap / MutationMap remain separate functions because read-only handling is determined by the destination (Params vs mutations).
Requirements
github.com/apstndb/spanvaluev0.7.1+ (gcvctor UTC-timestamp wire format). Formatting of GCVs is owned by spanvalue, so upgrading it alongside spanenc can change rendered output (for example FLOAT64 display); review those golden-test diffs separately from the spanenc adoption.github.com/apstndb/structfieldsv0.1.0 /structfields/spannertagv0.1.0.cloud.google.com/go/spanner: go.mod declares only the v1.84.1 MVS floor (mirrored semantics track v1.91.0); your module controls the actual client version.
v0.3.0-alpha.2
Pre-release adding result-set-shaped helpers surveyed from spanner-mycli / spannersh use cases (client-side virtual result sets such as SHOW-style outputs and status rows). Mirrored semantics still track cloud.google.com/go/spanner v1.91.0; go.mod stays at the v1.84.1 MVS floor.
Added
ResultSetMetadataFor[T]()/ResultSetMetadataFromGoType(reflect.Type)— wrapsRowTypeForinto*sppb.ResultSetMetadata, the shape expected by result-set consumers such asspanvalue/writer'sWithMetadata.RowEncoder[T](NewRowEncoder[T](maskOpts ...ColumnMaskOption)) — compiled row codec for streaming many rows of one struct type: field listing, column mask validation, and row type are resolved once at construction instead of per row.Columns() []string,RowType() (*sppb.StructType, error),ResultSetMetadata() (*sppb.ResultSetMetadata, error)(all mask-aware),Values(v T, opts ...EncodeOption) ([]spanner.GenericColumnValue, error).- Read-shaped like
StructColumnsAndValues(read-only fields included; an include mask may name them); a test pins the agreement betweenRowEncoderandStructColumnsAndValues. Tmay be a struct or pointer-to-struct type; a nil pointer row returnsErrNilStructPointer.- Includes a writer integration test streaming a SHOW VARIABLES-style virtual result set to CSV.
Notes
Everything from v0.3.0-alpha.1 (column masks, WithLossOfPrecisionHandling, ParamsMap) is included. Versioning policy (now recorded in AGENTS.md): the module stays on v0; breaking changes bump the minor version, everything else the patch version.
Requirements
github.com/apstndb/spanvaluev0.7.1+ (gcvctor UTC-timestamp wire format). Formatting of GCVs is owned by spanvalue, so upgrading it alongside spanenc can change rendered output (for example FLOAT64 display); review those golden-test diffs separately from the spanenc adoption.github.com/apstndb/structfieldsv0.1.0 /structfields/spannertagv0.1.0.cloud.google.com/go/spanner: go.mod declares only the v1.84.1 MVS floor (mirrored semantics track v1.91.0); your module controls the actual client version.
(Requirements section added 2026-06-11.)
v0.3.0-alpha.1
Pre-release adding column masks, per-call NUMERIC loss-of-precision control, and Statement.Params extraction. Mirrored semantics still track cloud.google.com/go/spanner v1.91.0; go.mod stays at the v1.84.1 MVS floor.
Added
- Column masks for the plain-Go-value helpers, written as either an include list or an exclude list (update-mask style):
WithColumns(columns ...string)— include mask; output keeps struct declaration order regardless of argument order.WithoutColumns(columns ...string)— exclude mask.- Apply to
MutationColumnsAndValues,MutationMap, andParamsMapvia the newColumnMaskOptiontype. Strict validation: unknown columns, read-only columns in a write-shaped include list, or combining include with exclude return the new sentinelErrInvalidColumnMask. Excluding a read-only column is a no-op.
WithLossOfPrecisionHandling(spanner.NumericError | spanner.NumericRound)(EncodeOption, accepted byValueOf,StructColumnsAndValues,ValuesFromSlice,ArrayValueFromSlice): per-call NUMERIC loss-of-precision control reusing the client's enum vocabulary. The client's package-globalspanner.LossOfPrecisionHandlingis never read; the per-call default isspanner.NumericError(validate, returningErrNumericOutOfRange), while the client's global defaults toNumericRound— pass it explicitly for silent rounding. Like the client, rounding affects only the fractional part.ParamsMap(v any, opts ...ColumnMaskOption) (map[string]any, error): struct → column-name-to-Go-value map for binding asspanner.StatementParams, with no GCV conversion (the client encodes at execution). Unlike the write-shapedMutationMap, read-only fields are included (they are ordinary bindable values) and an include mask may name them.
Changed
- Docs: corrected an inaccurate claim that the client's default loss-of-precision handling is
NumericError; the client's global actually defaults toNumericRound(silent rounding). spanenc's per-call default deliberately stays on validation.
Design notes
Two deliberate API decisions from review: option types stay split (EncodeOption vs ColumnMaskOption) so option applicability is fixed at compile time, and ParamsMap / MutationMap stay separate functions — the read-only handling is determined by the destination (Params vs mutations), so it is encoded in the function name rather than an option.
v0.2.0
Tracks the read-only struct tag feature from cloud.google.com/go/spanner and restructures the tag parser. Mirrored semantics now track spanner v1.91.0; the go.mod requirement intentionally stays at the v1.84.1 MVS floor (see "Dependency policy" below).
Added
- Read-only column tags (
spanner:"->",spanner:"Name;readonly",spanner:"Name;->"; case-insensitivereadonly), mirroring spanner v1.86.0 (googleapis/google-cloud-go#12895):- Tags now split on
;with the column name first (a tag likespanner:"Name;readonly"previously yielded the literal column nameName;readonly). - Read-shaped listings (
StructColumns,RowTypeFor,StructColumnsAndValues) include read-only fields, because they are readable viaToStruct. - Write-shaped helpers (
MutationColumnsAndValues,MutationMap) exclude them, mirroringstructToMutationParams. - STRUCT-typed values (
ValueOfon a struct,TypeFor) keep the client's raw-tag quirk: tag options leak verbatim into STRUCT field names.
- Tags now split on
- CI:
latest-depsjob tests againstcloud.google.com/go/spanner@latestto catch upstream drift early.
Changed
- The
spannertag parser is no longer local: it comes from the newgithub.com/apstndb/structfields/spannertagnested module (Apache-2.0 port of the client's unexportedspannerTag/spannerTagParser/fieldCache), preserving the invariant that upstream-derived code never lives in this MIT module. - Dependencies are now all tagged releases:
spanvaluev0.7.1 (gcvctor UTC-timestamp wire format; replaces the v0.1.x pseudo-version pin),structfieldsv0.1.0,structfields/spannertagv0.1.0.
Dependency policy
The mirrored-semantics version (v1.91.0) is independent of the go.mod requirement: go.mod declares only the minimum client version whose APIs this module uses (currently the v1.84.1 floor inherited from spanvalue), so downstream modules keep control of the spanner client version under MVS.
v0.1.1
Documentation-only release so pkg.go.dev shows the corrected README.
Changed
- README: link
github.com/apstndb/spanvalue/writerto pkg.go.dev with a short description (CSV/TSV/JSONL/SQL INSERT exporters for GCV rows) in the struct-streaming example. - README: trim the license section to "MIT" (the Apache-2.0 fork of upstream code lives separately in structfields).
No code changes; v0.1.0 remains the initial implementation. v0.1.0 was already cached by proxy.golang.org, so it was not re-tagged (re-tagging a published version causes permanent checksum mismatches).
v0.1.0
Initial release. Experimental: the API may change while encodeValue parity is proven against client library releases. Mirrored behavior tracks cloud.google.com/go/spanner v1.84.1.
Added
ValueOf(any) (spanner.GenericColumnValue, error)— Go value → GCV mirroring the client's internalencodeValue: all concrete parameter/mutation types (scalars,spanner.Null*wrappers includingsql.NullStringanduuid.NullUUID, pointers and slices with typed-NULL rules),spanner.Encoder, protobuf messages and enums (including typed-nil → typed NULL), named variants of base types (getDecodableSpannerTypebehavior), Go structs and struct slices as STRUCT values,spanner.CommitTimestamp, andGenericColumnValuepassthrough (deep clone).TypeFor[T]()/TypeFromGoType(reflect.Type)— static Go type →*sppb.Typeinference;ErrTypeNotInferablefor value-dependent types (spanner.Encoder,GenericColumnValue,NullProtoMessage/NullProtoEnum, interfaces).StructColumns[T]()/StructColumnsFromGoType—spanner-tag column names with the client's mutation/ToStructfield listing (embedded flattening, shadowing,spanner:"-"), the helper requested in googleapis/google-cloud-go#13800.RowTypeFor[T]()/RowTypeFromGoType— row-shaped*sppb.StructTypefor writer metadata.StructColumnsAndValues(any)— struct → column names + GCVs for spanvalue/writer.MutationColumnsAndValues(any)/MutationMap(any)— struct → plain Go cols/vals or map for the non-Struct mutation constructors (spanner.Update,spanner.UpdateMap, ...), enabling column masking by name.ValuesFromSlice[T]/ArrayValueFromSlice[T]— homogeneous slice → (element*sppb.Type,[]*structpb.Value) or ARRAY GCV; interface element types rejected.- Sentinel errors:
ErrUnsupportedType,ErrUntypedNil,ErrNotStruct,ErrNilStructPointer,ErrEmbeddedStructField,ErrTypeNotInferable,ErrInvalidSource,ErrNumericOutOfRange.
Semantics
Deliberate strictness divergences from the client are documented in the package documentation: untyped nil and nil struct pointers return errors, GCV inputs with nil Type are rejected, NUMERIC is always validated, and non-finite floats / JSON use gcvctor's canonical wire forms.
Dependencies
github.com/apstndb/structfieldsv0.1.0 — Apache-2.0 exported fork ofcloud.google.com/go/internal/fields; all upstream-derived code lives there, keeping spanenc MIT.github.com/apstndb/spanvalueis pinned to a pseudo-version of main (v0.7.1-0.20260610130420) because v0.7.0 predates gcvctor's UTC-timestamp fix; it will move to the next tagged release.