Skip to content

Releases: apstndb/spanvalue

v0.8.3

Choose a tag to compare

@apstndb apstndb released this 12 Jul 19:16
5c68bd5

Patch release since v0.8.2. Additive only.

Wire value extraction (#264)

WireValue exposes a GenericColumnValue's protobuf wire value for low-level ARRAY or STRUCT assembly, materializing an absent Value as an explicit protobuf NULL. Existing non-nil values are borrowed without cloning and must be treated as read-only.

WireValues applies the same contract to a GCV slice and returns a fresh result slice. The helpers replace duplicated wire extraction and NULL materialization in downstream memebridge integration code.

The WireValue / WireValues names replace the provisional ToProtoValue / ArrayWireValues names from issue #262: they avoid confusion with gcvctor.ProtoValue and apply equally to ARRAY and STRUCT assembly.

Full changelog

v0.8.2...v0.8.3

v0.8.2

Choose a tag to compare

@apstndb apstndb released this 27 Jun 19:52
13b9365

Patch release since v0.8.1. Additive only.

gcvctor: WithType helpers (#259)

WithType, WithEquivalentType, and WithExactType retype an existing GenericColumnValue using spantype equivalence checks.

Dependencies

  • Bump github.com/apstndb/spantype to v0.3.13 (tagged release; #260).

Full changelog

v0.8.1...v0.8.2

v0.8.1

Choose a tag to compare

@apstndb apstndb released this 12 Jun 09:50
aef12f9

Patch release since v0.8.0. Additive only.

gcvctor: PGNumericValueExact (#249 / #257)

PGNumericValueExact renders a *big.Rat as PG_NUMERIC at the exact decimal (smallest sufficient scale) instead of PGNumericValue's GoogleSQL-scale NumericString (9 fractional digits, silently rounding) — the PostgreSQL-dialect numeric value space is far wider, so exactness is refusable-loss territory: rationals without a finite decimal expansion return the new ErrInexactNumeric; nil returns ErrNilNumeric. MustPGNumericValueExact joins the fixture family. Feedback from apstndb/spanpg's dialect-adapter work, where the rounding had to be documented as a hazard.

Full changelog

v0.8.0...v0.8.1

v0.8.0

Choose a tag to compare

@apstndb apstndb released this 12 Jun 09:14
591721f

Breaking release. FormatConfig collapses to two fields — NullString and the ordered FormatComplexPlugins chain — completing the #250#253 redesign: every formatting concern is now a plugin, assembled by hand or through the validating NewFormatConfig builder. Preset outputs are byte-identical to v0.7.6 (pinned by the golden batteries). Closes #252, #253, #217, #185, #205, #219, #221, #250. Minimum Go remains 1.24.


Removed → replacement

Removed (deprecated in v0.7.6) Replacement
FormatConfig.FormatNullable append PluginFromNullable(f) last in the chain / NewFormatConfig(WithScalarFormatter(f), …)
FormatConfig.FormatArray PluginForArray / WithArrayFormat
FormatConfig.FormatStruct (+ the FormatStruct type, TypedStructFormat()) PluginForStruct / WithStructFormat; FormatTypedStruct is the exported paren function
FormatConfig.Literal literal quote options are constructor-captured plugin state (LiteralFormatConfigWithOptions / WithLiteralQuote signatures unchanged)
FormatLiteralValue (plugin value) LiteralValuePlugin(opts LiteralFormatOptions) constructor
FormatConfigWithoutScalarPlugins prepend a total PluginFromNullable(f) via WithComplexPlugin, or build with NewFormatConfig
ErrFormatNullableRequired, ErrNilFormatArray, ErrNilFormatStructField, ErrNilFormatStructParen, nil-callback panics, built-in ErrUnknownType for unknown codes one sentinel ErrUnhandledValue wrapping the type (coverage is a runtime property; Validate checks NullString + a non-empty, nil-free chain)
writer DelimitedWriter.Header / .UnnamedFieldNamer, JSONLWriter.UnnamedFieldNamer fields constructor-only WithHeader / WithUnnamedFieldNamer (#221)

Signature changes: FormatStructFieldFunc now takes Formatter instead of *FormatConfig; FormatComplexFunc and FormatNullableFunc are defined types instead of aliases (plain functions remain assignable). Formatter.GetNullString keeps its name (rename considered, declined — plugin-facing churn without payoff).

Internals deleted (#217 closed)

The function-pointer identity machinery (scalarFastPathActive, nullableFuncsEqual, the slow-path literal-quote interception) is gone: preset scalar plugins are unconditional chain members, and custom behavior enters the chain at an explicit position instead of replacing a field the framework then has to sniff.

JSON preset contract (#205 closed)

FormatJSONSimpleValue now gates on the supported scalar set, validates wire payloads, and falls through for unknown codes; stripped/escape-hatch configs error (ErrUnhandledValue) instead of silently emitting invalid JSON. JSON wire-as-is is documented as the contract and pinned with a denormalized-wire test; NULL renders via NullString: "null" as before.

New since v0.7.6

  • PluginForNullable[T] — the pre-composed PluginFromNullable(NullableFormatterFor(f)) for the dominant single-scalar-type override; annotation-aware via the Decode dispatch (PGNumeric matches only PG_NUMERIC).
  • ExampleNewFormatConfig and rewritten customization docs (doc.go).

Downstream impact (verified against all four consumers via local replaces before release)

  • spanner-mycli, execspansql: no changes required; builds and test suites pass unmodified.
  • spannersh: one line — the tuple-STRUCT recipe becomes SpannerCLICompatibleFormatConfig().WithComplexPlugin(PluginForStruct(FormatSimpleStructField, FormatTupleStruct)).
  • spanpg: PostgreSQLLiteralFormatConfig moves to NewFormatConfig (WithPlugin×2 preserving plugin order, WithArrayFormat, WithStructFormat with the field callback retyped to Formatter, WithScalarFormatter); its nested integration module needs no changes.
  • No consumer used the other removed identifiers; the alias→defined-type change was transparent everywhere.

Full changelog

v0.7.6...v0.8.0

v0.7.6

Choose a tag to compare

@apstndb apstndb released this 12 Jun 08:10
624500a

Final v0.7.x release. Additive plugin combinators and a validating config builder land the migration surface for the next breaking release (v0.8.0, #252/#253), with the affected FormatConfig fields now formally deprecated. Plus writer.RunRowSeqDeferredMetadata. No runtime behavior changes to existing call paths. Minimum Go remains 1.24.


Highlights

Per-type plugin combinators (#250 / #251, #254)

  • Guards: PluginForType (predicate, annotation/FQN-aware), PluginForTypeCode, PluginSkippingNull — lift the TypeCode/NULL boilerplate every FormatComplexFunc author hand-rolled. NULL stays deliberately un-pre-filtered in the chain (plugins may own type-specific NULL renderings); the combinator is the per-plugin opt-out. Dogfooded in-repo: the protofmt PROTO/ENUM plugins now use them (plugin-level note: typed NULL falls through instead of returning the null string from inside the plugin; chain-level output is unchanged on every preset).
  • Lifts: PluginFromNullable (the FormatNullable slow path as a chain plugin — same Decode dispatch incl. PG-annotated wrappers), NullableFormatterFor (single-wrapper-type formatter), PluginForArray, PluginForStruct (share the built-in branches' extraction and error classes; the struct field callback takes Formatter — the signature v0.8 aligns FormatStructFieldFunc to). Per-scalar-type overrides now compose with the chain as the base: SimpleFormatConfig().WithComplexPlugin(PluginFromNullable(NullableFormatterFor(...))).
  • New capability: plugins see NULL ARRAY/STRUCT values, so typed NULL renderings (CAST(NULL AS bigint[])) are now expressible — the retired callbacks structurally could not (#253).

NewFormatConfig builder (#253 / #254)

  • NewFormatConfig with WithNullString / WithPlugin (most-recent-first) / WithArrayFormat / WithStructFormat / WithScalarFormatter assembles a chain-only config (the v0.8 shape) in canonical order with build-time validation (ErrScalarFormatterRequired etc.). All four presets rebuilt through the builder are byte-identical across the full type battery (pinned by tests).
  • Validate() relaxation: nil FormatArray/FormatStruct/FormatNullable are accepted when FormatComplexPlugins is non-empty (plugins may cover those shapes).

Deprecations (removed in v0.8.0)

Deprecated Replacement
FormatConfig.FormatNullable append PluginFromNullable(f) last in the chain, or NewFormatConfig(WithScalarFormatter(f), …) (#252)
FormatConfig.FormatArray PluginForArray / WithArrayFormat
FormatConfig.FormatStruct PluginForStruct / WithStructFormat
FormatConfig.Literal literal preset constructor options (#185)

Lint will not warn you: staticcheck SA1019 does not flag deprecated struct fields used as composite-literal keys (FormatConfig{FormatArray: …}), which is how hand-built configs typically use them. Audit by inventory (grep the field names) rather than relying on lint; the v0.8.0 removal will otherwise surface as a compile error.

writer: RunRowSeqDeferredMetadata (#248)

  • RunRowSeq variant for row sources whose row type is known only after the first row.

Downstream verification

spanner-mycli, spannersh, execspansql, and spanpg (root + nested integration module) were built, tested, and linted against this release candidate via local replaces: all green, zero behavior diffs. spanpg is the only consumer using the deprecated fields directly (3 sites, migration inventoried).

Upgrading to v0.7.6

Compile-time: additive only. Runtime: no changes to existing call paths. Plan the v0.8.0 migration with the deprecations table above.

Full changelog

v0.7.5...v0.7.6

v0.7.5

Choose a tag to compare

@apstndb apstndb released this 11 Jun 15:40
4236573

Patch release since v0.7.4. Correctness fixes (GoogleSQL identifier quoting, writer error latching, error classification), additive gcvctor / protofmt APIs, dbsqlrows semantics hardening, docs, and CI. Minimum Go remains 1.24.


Highlights

dialect: GoogleSQL identifier escaping fix (#200 / #239)

  • QuoteIdentifier now uses GoogleSQL string-literal escapes (\\\, `\`) instead of MySQL-style backtick doubling. A trailing backslash could previously escape the closing backtick (injection-shaped failure for SQL INSERT export with hostile identifiers). PostgreSQL quoting is unchanged.

writer: sticky write errors and whole-statement SQL emission (#204 / #246)

  • All three writers latch the first output write failure; subsequent Write*/Flush calls return it and write nothing — the documented discard-after-write-error contract is now enforced instead of risking silent output corruption.
  • SQL INSERT statements are built in memory and emitted with a single Write, so a mid-tuple I/O failure can no longer leave dangling output that a later nil-returning Flush terminates into invalid SQL. Batched rows are emitted at the batch boundary or Flush rather than streamed per row.
  • New ErrInvalidSQLInsertKind: out-of-range WithSQLInsertKind values are rejected at construction instead of silently emitting plain INSERT.
  • DelimitedWriter.Flush after a late Header = true no longer strands buffered rows behind ErrHeaderAfterData.

spanvalue: ErrMalformedWire sentinel (#216 / #245)

  • Malformed wire payloads (wrong structpb kind, unexpected NULL in the wire validator, nil Type with a value) now wrap the new ErrMalformedWire instead of ErrUnknownType, which is reserved for genuinely unknown type codes. Migrated sites include the PROTO/ENUM CAST wire-kind rejections (v0.7.1 #228) and protofmt's non-string wire rejection. errors.Is(err, ErrUnknownType) consumers: reclassified cases no longer match; match ErrMalformedWire for data problems.

gcvctor: remaining constructor gaps (#207 / #243)

  • PGOIDValue (PG-dialect PG.OID, decimal-string wire).
  • Validated string inputs: UUIDStringValue (canonicalizes case/braced/URN forms) and JSONStringValue (validates, stores wire as-is; new ErrInvalidJSON).
  • Must* completions: MustArrayValue, MustNumericValueChecked, MustPGNumericValueChecked, MustUUIDStringValue, MustJSONStringValue.

protofmt: OnUnresolved handler (#180 / #244)

  • Opt-in OnUnresolved func(typeFQN string, code sppb.TypeCode) error on both option types: invoked when a non-nil resolver fails to resolve a non-NULL PROTO/ENUM value. Return an error for strict mode, nil to keep the lenient fallthrough; nil handler = unchanged behavior. Runnable example included.

dbsqlrows (experimental): semantics hardening (#209, #210 / #242)

  • RowsRead stays zero when WriteDataRow is nil, matching writer.RowIteratorResult.RowsRead.
  • New ErrMissingStatsResultSet replaces silent nil Stats when ReadResultSetStats is requested without driver-side stats; scanned stats are preserved when the trailing NextResultSet advance fails.

Documentation and CI

  • dbsqlrows godoc renders correctly on pkg.go.dev; REPL guidance moved to dbsqlrows/README.md (#211 / #241).
  • govulncheck is PR-blocking with a clean symbol-level baseline; make vulncheck for local parity (#177 / #240, toolchain-resolution fix in #247).

Upgrading to v0.7.5

Compile-time: additive APIs only; no removals or signature changes.

Runtime behavior changes (review if you use these patterns):

Area v0.7.4 v0.7.5
QuoteIdentifier (GoogleSQL) with backtick/backslash in the name MySQL-style doubling; trailing \ broke quoting GoogleSQL string-literal escapes (#239)
Writer call after an output write error Could keep writing / Flush could return nil on corrupt output Sticky error returned by every later call (#246)
Batched SQL INSERT emission Streamed per row Whole statement per Write at batch boundary / Flush (#246)
WithSQLInsertKind(SQLInsertKind(99)) Plain INSERT silently ErrInvalidSQLInsertKind at construction (#246)
Malformed wire payloads errors.Is(err, ErrUnknownType) errors.Is(err, ErrMalformedWire) (#245)
dbsqlrows RowsRead with nil WriteDataRow Counted drained rows Stays zero (#242)
dbsqlrows stats requested but driver stats disabled Silent Stats == nil ErrMissingStatsResultSet (#242)

Full changelog

v0.7.4...v0.7.5

v0.7.4

Choose a tag to compare

@apstndb apstndb released this 11 Jun 04:49
cc78475

Patch release since v0.7.3. Additive writer APIs for rows that do not come from a *spanner.RowIterator, plus documentation. No compile-time API removals, signature changes, or runtime behavior changes to existing APIs. Minimum Go remains 1.24.


Highlights

writer: RunRowSeq / WriteRowSeq for rows without a RowIterator (#238)

  • RunRowSeq and WriteRowSeq drive RowIteratorHooks / RowIteratorWriter sinks from client-side (virtual) result sets: explicit *sppb.ResultSetMetadata plus a fallible row sequence (iter.Seq2[*spanner.Row, error]).
  • The hook contract is identical to RunRowIterator by construction (shared internal loop): PrepareMetadata runs once before the first data row including for an empty sequence (header-only delimited output), a yielded error aborts without Finish, RowsRead counts successful writes, Stats stays zero.
  • RowSeq adapts pre-built rows to the sequence shape. New ErrNilRowSeq; a (nil, nil) pair yielded by a sequence is rejected at the boundary with ErrNilRow.
  • Pairs with lazy per-row encoders that can fail — designed against spanenc RowEncoder.Rows (apstndb/spanenc#2), surveyed from spanner-mycli SHOW/HELP virtual result sets (apstndb/spanner-mycli#657).

Documentation

  • ExampleWriteRowSeq (runnable, pkg.go.dev), writer godoc # RowIterator section, and writer/README.md goal-table row for in-memory / virtual rows.

Upgrading to v0.7.4

From v0.7.3

Compile-time: additive APIs only (RunRowSeq, WriteRowSeq, RowSeq, ErrNilRowSeq).

Runtime: no behavior changes to existing APIs.


Full changelog

v0.7.3...v0.7.4

v0.7.3

Choose a tag to compare

@apstndb apstndb released this 11 Jun 02:51
77bc274

Patch release since v0.7.2. Fixes wire output from JSONFromNullable and PGJSONBFromNullable when Value is a Go string, aligning with the official Spanner client's encodeValue. No compile-time API removals or signature changes. Minimum Go remains 1.24.


Highlights

gcvctor JSON nullable wire fix (#237, #236)

Input Value v0.7.2 wire v0.7.3 wire
"x" (string) x (invalid JSON) "x"
json.RawMessage("{\"a\":1}") {"a":1} {"a":1}

Upgrading to v0.7.3

From v0.7.2

Compile-time: no API removals or signature changes.

Runtime: if you relied on JSONFromNullable / PGJSONBFromNullable treating a Go string Value as pre-encoded wire JSON, switch that input to json.RawMessage. This matches the official client's encoding semantics and fixes invalid wire output for string column round-trips (e.g. spanenc adoption of #232).


Full changelog

v0.7.2...v0.7.3

v0.7.2

Choose a tag to compare

@apstndb apstndb released this 11 Jun 02:12
1b92e44

Patch release since v0.7.1. Additive gcvctor constructors for string-based and nullable inputs, documentation for gcvctor traps and the spanenc adoption boundary, deprecation of JSONObjectStructFormat, and internal dead-code cleanup. No compile-time API removals or signature changes. Minimum Go remains 1.24.


Highlights

gcvctor string-based and nullable constructors (#232)

  • StringBasedValueOf and StringBasedValueFromCode construct GCVs from wire strings with caller-supplied types (no validation beyond type assignment).
  • Nullable input helpers for spanenc-style construction: pointer-based (BoolFromPtr, Int64FromPtr, Float64FromPtr, Float32FromPtr, StringFromPtr, BytesFromSlice, DateFromPtr, TimestampFromPtr, UUIDFromPtr, IntervalFromPtr) and spanner.Null* wrappers (BoolFromNullable, Int64FromNullable, Float64FromNullable, Float32FromNullable, StringFromNullable, DateFromNullable, TimestampFromNullable, UUIDFromNullable, IntervalFromNullable, NumericFromNullable, JSONFromNullable, PGNumericFromNullable, PGJSONBFromNullable).

Documentation (#222, #233)

  • gcvctor package docs: wire-string traps, StringBasedValueOf semantics, and when to use explicit constructors vs spanenc.
  • Root package and AGENTS.md: spanenc adoption boundary (reflection / client-tag encoding stays in spanenc; gcvctor remains explicit and strict).
  • Godoc gaps filled for dialect helpers, literal quoting, and JSON row formatting.

JSONObjectStructFormat deprecated (#215)

Internal cleanup (#214)

  • Removed unused literal helper functions from internal/ (no public API impact).

Upgrading to v0.7.2

From v0.7.1

Compile-time: additive APIs only. No signature removals or renames.

Runtime: no intentional behavior changes to formatting presets or export paths. Existing JSONObjectStructFormat call sites continue to work; prefer NewJSONObjectStructFormatter for new code.


Full changelog

v0.7.1...v0.7.2

v0.7.1

Choose a tag to compare

@apstndb apstndb released this 10 Jun 14:59
cd4dbe6

Patch release since v0.7.0. Correctness fixes for gcvctor wire strings, SQL INSERT zero-column writes, Spanner CLI / Literal float formatting, and ENUM CAST validation. Documentation-only updates for godoc accuracy and Gemini review styleguide. No compile-time API removals or signature changes. Minimum Go remains 1.24.


Highlights

gcvctor wire string correctness (#225)

  • TimestampValue normalizes to UTC Zulu wire strings (matches Spanner client and TimestampStringValue).
  • JSONValue and PGJSONBValue marshal without HTML character escaping, matching Spanner-emitted JSON wire for fixtures.
  • dbsqlrows/gospanner require bumped to spanvalue v0.7.0 (nested module metadata only).

SQL INSERT zero-column guard (#226)

  • SQLInsertWriter WriteValues / WriteRow now return ErrMissingColumnNames for a registered empty row type, matching WriteGCVs and preventing invalid INSERT INTO t () VALUES (); output.

Float and ENUM literal formatting (#228 / #206)

  • Literal preset: finite integral FLOAT64 values append .0 when the shortest strconv form would lex as INT64 (e.g. 1.0 not 1).
  • Spanner CLI preset: integral finite floats omit the fractional part (1 not 1.000000); non-integral values keep six decimal places (matches spanner-cli).
  • FormatEnumAsCast validates ENUM wire payloads with ParseInt before emitting CAST(... AS ...) SQL.

Documentation (#227, #229)

  • Godoc accuracy sweep for writer, gcvctor, and dbsqlrows (no behavior change).
  • Gemini styleguide: clarify [Type.Method] vs [*Type.Method] doc links.

Upgrading to v0.7.1

From v0.7.0

Compile-time: no signature removals or renames. Additive test coverage only.

Runtime behavior changes (review if you use these patterns or assert golden strings):

Area v0.7.0 v0.7.1
gcvctor.TimestampValue with non-UTC time.Time Local offset in wire string UTC Zulu wire (#225)
gcvctor.JSONValue / PGJSONBValue with <, >, & in JSON HTML-escaped wire (\u003c etc.) Unescaped wire matching Spanner (#225)
SQLInsertWriter write after zero-column PrepareRowType Could emit invalid INSERT ... () VALUES () ErrMissingColumnNames (#226)
Literal preset integral FLOAT64 (e.g. 1.0) Shortest form without .0 1.0 when needed to avoid INT64 lex (#228)
Spanner CLI preset integral floats 1.000000 1 (#228)
ENUM CAST with non-integer wire string Could emit invalid SQL Parse error (#228)

Preset-backed Simple export and typical CSV/JSONL paths are unaffected. Re-golden Literal, Spanner CLI, or hand-built gcvctor fixture tests if you assert the rows above.


Full changelog

v0.7.0...v0.7.1