Documentation
¶
Overview ¶
Package spanenc converts plain Go values into cloud.google.com/go/spanner.GenericColumnValue (GCV) values and derives column names and Spanner types from Go structs, following the Cloud Spanner Go client library's own encoding semantics — the `spanner` struct tag rules and the Go type coverage of statement parameters and mutations.
The client library keeps its encoding internal (encodeValue, structToMutationParams, and the internal fields cache); this package mirrors those semantics on top of github.com/apstndb/spanvalue/gcvctor constructors so the results compose with the spanvalue formatting and writer stack. The mirrored behavior tracks cloud.google.com/go/spanner v1.84.1.
API overview ¶
- ValueOf: Go value → GCV, mirroring encodeValue (typed NULLs from nil pointers and nil slices, spanner.Null* wrappers, spanner.Encoder, protobuf messages and enums, named variants of base types, Go structs as STRUCT values, spanner.CommitTimestamp).
- TypeFor / TypeFromGoType: Go type → cloud.google.com/go/spanner/apiv1/spannerpb.Type, the type half of ValueOf without a value.
- StructColumns / StructColumnsFromGoType: `spanner`-tagged column names from a struct type, as requested in https://github.com/googleapis/google-cloud-go/issues/13800.
- RowTypeFor / RowTypeFromGoType: row-shaped cloud.google.com/go/spanner/apiv1/spannerpb.StructType for writer metadata.
- StructColumnsAndValues: one struct → column names + GCVs, for GCV-level consumers such as github.com/apstndb/spanvalue/writer.
- MutationColumnsAndValues / MutationMap: one struct → cols/vals or map with plain Go values, for the non-Struct mutation constructors (cloud.google.com/go/spanner.Update, cloud.google.com/go/spanner.UpdateMap, ...), enabling column masking by name.
- ValuesFromSlice / ArrayValueFromSlice: homogeneous slices → (element type, wire values) or an ARRAY GCV; heterogeneous-capable (interface) element types are rejected.
Struct field listings ¶
Following the client, there are two different struct field listings:
- Row-shaped helpers (StructColumns, RowTypeFor, StructColumnsAndValues, MutationColumnsAndValues, MutationMap) use the mutation/ToStruct listing: exported fields, embedded struct fields flattened with Go's shadowing rules, `spanner:"-"` skipped, declaration order.
- STRUCT-typed values (ValueOf on a struct, TypeFor) use the encodeStruct listing: declaration order, embedded fields rejected with ErrEmbeddedStructField, and `spanner:""` producing an unnamed field.
Divergences from the client library ¶
This package is strict where the client is lenient, so malformed GCVs never enter the spanvalue stack:
- Untyped nil returns ErrUntypedNil; the client sends a NULL without type information.
- A nil pointer to struct passed to MutationColumnsAndValues, MutationMap, or StructColumnsAndValues returns ErrNilStructPointer; the client silently builds an empty mutation.
- cloud.google.com/go/spanner.GenericColumnValue inputs with a nil Type are rejected.
- NUMERIC precision is always validated (the client validates only under its default NumericError loss-of-precision handling, which is also the behavior mirrored here).
- Non-finite FLOAT64/FLOAT32 values and JSON payloads use the canonical wire forms produced by github.com/apstndb/spanvalue/gcvctor ("NaN"/"Infinity" strings; compact JSON without HTML escaping); the client sends a raw protobuf NumberValue and HTML-escaped JSON. Both forms are semantically equivalent and accepted by Spanner.
The package is experimental: the API may change while encodeValue parity is being proven against client library releases.
Index ¶
- Variables
- func ArrayValueFromSlice[T any](vs []T) (spanner.GenericColumnValue, error)
- func MutationColumnsAndValues(v any) ([]string, []any, error)
- func MutationMap(v any) (map[string]any, error)
- func RowTypeFor[T any]() (*sppb.StructType, error)
- func RowTypeFromGoType(t reflect.Type) (*sppb.StructType, error)
- func StructColumns[T any]() ([]string, error)
- func StructColumnsAndValues(v any) ([]string, []spanner.GenericColumnValue, error)
- func StructColumnsFromGoType(t reflect.Type) ([]string, error)
- func TypeFor[T any]() (*sppb.Type, error)
- func TypeFromGoType(t reflect.Type) (*sppb.Type, error)
- func ValueOf(v any) (spanner.GenericColumnValue, error)
- func ValuesFromSlice[T any](vs []T) (*sppb.Type, []*structpb.Value, error)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrUnsupportedType is returned when a Go value or type has no Cloud Spanner // client library encoding. It corresponds to the client's // "client doesn't support Go type" errors from encodeValue. ErrUnsupportedType = errors.New("spanenc: unsupported Go type") // ErrUntypedNil is returned by [ValueOf] for an untyped nil input. // This is a deliberate divergence from the client library, which encodes // untyped nil as a NULL value WITHOUT type information; spanvalue/gcvctor // consumers require a well-formed Type in every GenericColumnValue. // Use a typed nil (for example (*int64)(nil)) or // [github.com/apstndb/spanvalue/gcvctor.NullOf] instead. ErrUntypedNil = errors.New("spanenc: untyped nil value") // ErrNotStruct is returned by struct-shaped helpers ([StructColumns], // [RowTypeFor], [StructColumnsAndValues], and variants) when the input is // not a Go struct or pointer to struct. It mirrors the client's // errNotStruct from structToMutationParams. ErrNotStruct = errors.New("spanenc: not a Go struct type") // ErrNilStructPointer is returned by [StructColumnsAndValues] for a nil // pointer to struct. This is a deliberate divergence from the client's // structToMutationParams, which silently returns empty columns and values; // silently producing an empty row is a footgun for export use cases. ErrNilStructPointer = errors.New("spanenc: nil struct pointer") // ErrEmbeddedStructField is returned when a Go struct with embedded // (anonymous) fields is encoded as a Spanner STRUCT value. It mirrors the // client's errUnsupportedEmbeddedStructFields in encodeStruct. Note that // the row-shaped helpers ([StructColumns], [RowTypeFor], // [StructColumnsAndValues]) flatten embedded fields instead, mirroring the // client's mutation/ToStruct field listing. ErrEmbeddedStructField = errors.New("spanenc: embedded struct fields are not supported in STRUCT values") // ErrTypeNotInferable is returned by [TypeFor], [TypeFromGoType], and the // slice helpers when the Spanner type cannot be derived from the Go type // alone: types implementing [cloud.google.com/go/spanner.Encoder], // [cloud.google.com/go/spanner.GenericColumnValue], // [cloud.google.com/go/spanner.NullProtoMessage], // [cloud.google.com/go/spanner.NullProtoEnum], and interface types. The // client library never needs a type-only path because it always encodes // concrete values. ErrTypeNotInferable = errors.New("spanenc: Spanner type is not inferable from the Go type alone") // ErrInvalidSource is returned when a value cannot represent a typed NULL // and is invalid, mirroring the client's errNotValidSrc: NullProtoMessage // and NullProtoEnum with Valid == false. ErrInvalidSource = errors.New("spanenc: invalid (NULL) source value") // ErrNumericOutOfRange is returned when a NUMERIC input exceeds the // precision or scale supported by Cloud Spanner, mirroring the client's // validateNumeric under the default NumericError loss-of-precision // handling. ErrNumericOutOfRange = errors.New("spanenc: NUMERIC value exceeds supported precision or scale") )
Functions ¶
func ArrayValueFromSlice ¶
func ArrayValueFromSlice[T any](vs []T) (spanner.GenericColumnValue, error)
ArrayValueFromSlice converts a homogeneous slice into an ARRAY spanner.GenericColumnValue with the element type inferred statically from T (see ValuesFromSlice for the homogeneity rules). Following the client library's slice handling, a nil slice becomes a typed NULL ARRAY and an empty slice an empty ARRAY.
func MutationColumnsAndValues ¶
MutationColumnsAndValues extracts column names and plain Go field values from a struct (or non-nil pointer to struct), mirroring the client's structToMutationParams. The results fit the cols/vals form of mutation constructors such as spanner.Insert, spanner.Update, and spanner.Replace, so callers can mask columns by name before building the mutation — something the *Struct constructors cannot do.
Values are returned as-is (no GCV conversion); the client library encodes them when the mutation is applied, so this helper accepts whatever InsertStruct accepts. A nil pointer returns ErrNilStructPointer where the client would silently produce an empty mutation.
Example ¶
ExampleMutationColumnsAndValues masks columns by name before building a plain cols/vals mutation, which the *Struct mutation constructors cannot express.
package main
import (
"fmt"
"slices"
"cloud.google.com/go/spanner"
"github.com/apstndb/spanenc"
)
func main() {
type Singer struct {
SingerID int64 `spanner:"SingerId"`
FirstName string
LastName string
}
cols, vals, err := spanenc.MutationColumnsAndValues(Singer{SingerID: 1, FirstName: "Marc", LastName: "Richards"})
if err != nil {
fmt.Println(err)
return
}
// Update only SingerId and LastName, masking FirstName.
var mcols []string
var mvals []any
for i, c := range cols {
if !slices.Contains([]string{"SingerId", "LastName"}, c) {
continue
}
mcols = append(mcols, c)
mvals = append(mvals, vals[i])
}
_ = spanner.Update("Singers", mcols, mvals)
fmt.Println(mcols)
fmt.Println(mvals)
}
Output: [SingerId LastName] [1 Richards]
func MutationMap ¶
MutationMap extracts a column-name-to-Go-value map from a struct (or non-nil pointer to struct) for the *Map mutation constructors (spanner.InsertMap, spanner.UpdateMap, spanner.ReplaceMap, spanner.InsertOrUpdateMap). Masking a column is a map delete away.
Duplicate column names (possible with explicit duplicate `spanner` tags) return an error rather than silently dropping a value.
func RowTypeFor ¶
func RowTypeFor[T any]() (*sppb.StructType, error)
RowTypeFor returns the sppb.StructType describing a row of T, pairing StructColumns names with statically inferred field types (see TypeFromGoType). It suits writer metadata such as github.com/apstndb/spanvalue/writer's WithRowType, or the row_type of a ResultSetMetadata.
Note that this row-shaped view flattens embedded struct fields, while a STRUCT-typed value of T (TypeFor, ValueOf) rejects them; the client library has the same split between mutations/ToStruct and STRUCT parameters.
func RowTypeFromGoType ¶
func RowTypeFromGoType(t reflect.Type) (*sppb.StructType, error)
RowTypeFromGoType is RowTypeFor for a reflect.Type.
func StructColumns ¶
StructColumns returns the column names derived from T's fields and `spanner` tags, in declaration order, with the same field listing the client library uses for mutations (cloud.google.com/go/spanner.InsertStruct) and cloud.google.com/go/spanner.Row.ToStruct: exported fields only, embedded struct fields flattened, `spanner:"-"` skipped.
It is the spanvalue-side answer to the StructColumns helper requested in https://github.com/googleapis/google-cloud-go/issues/13800, typically used to build the columns argument of Read calls from the same struct passed to ToStruct. T may be a struct or pointer-to-struct type.
Example ¶
ExampleStructColumns derives Read columns from the same struct used with Row.ToStruct, the use case of https://github.com/googleapis/google-cloud-go/issues/13800.
package main
import (
"fmt"
"github.com/apstndb/spanenc"
)
func main() {
type Singer struct {
SingerID int64 `spanner:"SingerId"`
FirstName string
LastName string
Internal string `spanner:"-"`
}
columns, err := spanenc.StructColumns[Singer]()
if err != nil {
fmt.Println(err)
return
}
fmt.Println(columns)
// client.Single().Read(ctx, "Singers", spanner.AllKeys(), columns) ...
Output:
func StructColumnsAndValues ¶
func StructColumnsAndValues(v any) ([]string, []spanner.GenericColumnValue, error)
StructColumnsAndValues converts a struct (or non-nil pointer to struct) into parallel column-name and spanner.GenericColumnValue slices using the client's mutation field listing (see StructColumns) and ValueOf for each field. The result feeds GCV-level consumers such as github.com/apstndb/spanvalue/writer's WriteValues.
For the client library's own mutation constructors, use MutationColumnsAndValues or MutationMap instead, which keep plain Go values and let the client encode them.
Example ¶
ExampleStructColumnsAndValues streams Go structs to CSV through spanvalue/writer.
package main
import (
"fmt"
"os"
"github.com/apstndb/spanvalue/writer"
"github.com/apstndb/spanenc"
)
func main() {
type Singer struct {
SingerID int64 `spanner:"SingerId"`
FirstName string
}
names, values, err := spanenc.StructColumnsAndValues(Singer{SingerID: 1, FirstName: "Marc"})
if err != nil {
fmt.Println(err)
return
}
w, err := writer.NewCSVWriter(os.Stdout, writer.WithColumnNames(names))
if err != nil {
fmt.Println(err)
return
}
if err := w.WriteValues(names, values); err != nil {
fmt.Println(err)
return
}
if err := w.Flush(); err != nil {
fmt.Println(err)
return
}
}
Output: SingerId,FirstName 1,Marc
func StructColumnsFromGoType ¶
StructColumnsFromGoType is StructColumns for a reflect.Type.
func TypeFor ¶
TypeFor returns the sppb.Type that ValueOf would produce for a non-NULL value of Go type T, following the Cloud Spanner client library encoding semantics. See TypeFromGoType.
func TypeFromGoType ¶
TypeFromGoType returns the sppb.Type that ValueOf produces for values of the given Go type, following the Cloud Spanner client library encoding semantics (encodeValue in cloud.google.com/go/spanner).
It returns ErrTypeNotInferable for Go types whose Spanner type depends on the value rather than the type: interface types, cloud.google.com/go/spanner.Encoder implementations, cloud.google.com/go/spanner.GenericColumnValue, cloud.google.com/go/spanner.NullProtoMessage, and cloud.google.com/go/spanner.NullProtoEnum. It returns ErrUnsupportedType for Go types the client library cannot encode.
func ValueOf ¶
func ValueOf(v any) (spanner.GenericColumnValue, error)
ValueOf converts a Go value into a spanner.GenericColumnValue, following the Cloud Spanner client library encoding semantics (encodeValue in cloud.google.com/go/spanner): the Go types accepted for statement parameters and mutation values, including the typed NULL rules (nil pointers and nil slices become typed NULLs), spanner.Null* wrappers, spanner.Encoder implementations, protobuf messages and enums, named variants of base types, Go structs as STRUCT values, and spanner.CommitTimestamp.
Deliberate divergences from the client library are listed in the package documentation; most notably an untyped nil returns ErrUntypedNil instead of a NULL without type information.
Example ¶
ExampleValueOf encodes Go values with the client library's parameter semantics and formats them with spanvalue.
package main
import (
"fmt"
"github.com/apstndb/spanvalue"
"github.com/apstndb/spanenc"
)
func main() {
for _, v := range []any{
"foo",
(*int64)(nil),
[]string{"a", "b"},
} {
gcv, err := spanenc.ValueOf(v)
if err != nil {
fmt.Println(err)
return
}
s, err := spanvalue.FormatColumnLiteral(gcv)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(s)
}
}
Output: "foo" NULL ["a", "b"]
func ValuesFromSlice ¶
ValuesFromSlice converts a homogeneous slice into the shared element sppb.Type and one wire structpb.Value per element. Homogeneity is enforced through the static element type: T must have a type-inferable Spanner type (see TypeFromGoType), so interface element types — which could hold heterogeneous values — are rejected with ErrTypeNotInferable before any element is examined. Each encoded element is additionally verified against the inferred type.
A nil slice returns the element type with a nil values slice. To express a typed NULL ARRAY versus an empty ARRAY at the GCV level, use ArrayValueFromSlice.
Types ¶
This section is empty.