spanvalue

package module
v0.7.0-alpha.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 9, 2026 License: MIT Imports: 17 Imported by: 1

README

spanvalue

Go Reference

Helpers for working with Cloud Spanner’s spanner.GenericColumnValue and related client types: format values to text (literals, JSON, CLI-style output) and construct values from Go types.

Package Role
github.com/apstndb/spanvalue Format spanner.GenericColumnValue and *spanner.Row using FormatConfig and presets such as LiteralFormatConfig, JSONFormatConfig, SpannerCLICompatibleFormatConfig.
github.com/apstndb/spanvalue/gcvctor Build spanner.GenericColumnValue (scalars, ARRAY, STRUCT, typed nulls). Types are often composed with github.com/apstndb/spantype/typector.
github.com/apstndb/spanvalue/protofmt Opt-in descriptor-aware PROTO and ENUM display plugins for FormatConfig.
github.com/apstndb/spanvalue/writer Stream Spanner rows to CSV, TSV, JSONL, or SQL INSERT (writer/README.md).
github.com/apstndb/spanvalue/dbsqlrows Experimental. Driver-agnostic database/sql export — see package documentation.

Identifier quoting helpers

QuoteIdentifier and QuoteQualifiedIdentifier are conservative quoting helpers. They always quote for the selected dialect, escape embedded quote characters, and do not attempt a minimal "quote only when necessary" strategy.

  • DATABASE_DIALECT_UNSPECIFIED follows the Spanner default and uses GoogleSQL quoting.
  • QuoteQualifiedIdentifier quotes each dotted path segment independently.
  • The helpers do not validate empty identifiers or empty path segments; callers that reject those shapes must do so before calling them.
quotedTable := spanvalue.QuoteQualifiedIdentifier(
    databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL,
    "analytics.daily_metrics",
)
quotedColumn := spanvalue.QuoteIdentifier(
    databasepb.DatabaseDialect_GOOGLE_STANDARD_SQL,
    "select",
)
// quotedTable == "`analytics`.`daily_metrics`"
// quotedColumn == "`select`"

Tuple-style STRUCT with Spanner CLI scalars

SpannerCLICompatibleFormatConfig matches official spanner-cli output, including bracket-style STRUCT in arrays ([[1, east]]). For tuple parentheses ([(1, east)]) while keeping CLI scalar rules, clone the preset and set FormatTupleStruct:

fc := spanvalue.SpannerCLICompatibleFormatConfig().Clone()
fc.FormatStruct.FormatStructParen = spanvalue.FormatTupleStruct

See ExampleSpannerCLICompatibleFormatConfig_tupleStruct. Keep product-specific combinations in your application (not as new spanvalue presets).

Hand-built FormatConfig

Preset constructors (LiteralFormatConfig, SimpleFormatConfig, and others) return configs that pass FormatConfig.Validate. After hand-assembling or mutating a config—Clone() then edit FormatComplexPlugins, or strip scalar plugins with FormatConfigWithoutScalarPlugins—call Validate before the first format or export so nil callbacks and an empty NullString fail at construction time rather than on the first row.

Custom scalar plugins in FormatComplexPlugins are not detected by Validate; keep FormatNullable set when using them. Writers accept any *FormatConfig via writer.WithFormatter but do not call Validate today—validate hand-built formatters before passing them to writers.

fc := spanvalue.SimpleFormatConfig().Clone()
fc.FormatComplexPlugins = append(fc.FormatComplexPlugins, myPlugin)
if err := fc.Validate(); err != nil {
	return err
}

Adoption snippets

Use the small helper APIs directly when replacing ad hoc downstream formatting code:

jsonLine, err := spanvalue.FormatRowJSONObjectFromColumns(
    spanvalue.JSONFormatConfig(),
    columnNames,
    gcvs,
    spanvalue.IndexedUnnamedFieldNamer,
)
w, err := writer.NewSQLInsertWriter(out, "analytics.daily_metrics")
if err != nil {
	return err
}
if err := w.WriteValues(columnNames, gcvs); err != nil {
    return err
}
return w.Flush()

Nested ARRAY/STRUCT test fixtures: gcvctor.MustStructValueOf and gcvctor.MustArrayValueOf wrap the error-returning constructors and panic on construction errors—use only in tests with schema-known inputs (see gcvctor package docs).

elemType := typector.CodeToSimpleType(sppb.TypeCode_STRING)
row := gcvctor.MustStructValueOf(
	[]string{"id", "tags"},
	[]spanner.GenericColumnValue{
		gcvctor.Int64Value(1),
		gcvctor.MustArrayValueOf(elemType, gcvctor.StringValue("a"), gcvctor.StringValue("b")),
	},
)

Streaming row exports

Package writer streams *spanner.Row, structpb cells, or []spanner.GenericColumnValue to CSV, quoted TSV, JSONL, or SQL INSERT using spanvalue formatters. writer/README.md covers RowIterator lifecycle (WriteRowIterator, RunRowIterator, hooks and decorators, RowsRead), schema registration, format edge cases, and v0.5.x expectations if writer becomes a separate module.

When every query returns at least one row, iter.Do plus WriteRow and Flush is enough (the first row registers column names). When a SELECT may return zero rows but metadata still lists columns, use WriteRowIterator (see writer README).

iter := txn.Query(ctx, stmt)

w, err := writer.NewCSVWriter(out, writer.WithFormatter(cfg))
if err != nil {
	return err
}
if err := iter.Do(func(row *spanner.Row) error {
	return w.WriteRow(row)
}); err != nil {
	return err
}
return w.Flush()
go-sql-spanner and GenericColumnValue export

Writer package details (GCV export options, RowIterator vs WriteGCVs): writer/README.md.

go-sql-spanner apps often decode query rows into []spanner.GenericColumnValue (for example via proto decode options) and export with spanvalue writers: scan → GCV slice → writer.WriteGCVs.

Column names: database/sql does not surface Spanner *spannerpb.ResultSetMetadata; register columns with writer.WithColumnNames from your scan metadata (or rows.Columns() plus any unnamed-field policy). When the app already holds *spannerpb.ResultSetMetadata (for example proto decode), writer.WithMetadata is appropriate. For display headers outside the writer, use spanvalue.ColumnNames on the same field list with the same spanvalue.UnnamedFieldNamer as writer.WithUnnamedFieldNamer.

Duplicate aliases: explicit duplicate field names from Spanner (for example SELECT 1 AS a, 2 AS a) are preserved in ColumnNames output (["a", "a"]). Only UnnamedFieldNamer collisions return errors. CSV/TSV headers and JSONL field names follow the same resolved names; see writer/README.md.

CSV / JSONL: register schema and formatting at construction, stream rows, then Flush (CSV may emit a header on zero-row SELECT; JSONL Flush is a no-op).

When the app already holds *spannerpb.ResultSetMetadata (for example from proto decode), pass metadata, formatter, and namer together with writer.DelimitedGCVExportOptions or writer.JSONLGCVExportOptions (nil arguments are skipped):

w, err := writer.NewCSVWriter(out, writer.DelimitedGCVExportOptions(
	metadata,
	spanvalue.SimpleFormatConfig(),
	spanvalue.IndexedUnnamedFieldNamer,
)...)
if err != nil {
	return err
}
defer rows.Close()
for rows.Next() {
	var gcvs []spanner.GenericColumnValue
	// decode the scanned row into gcvs
	if err := w.WriteGCVs(gcvs); err != nil {
		return err
	}
}
if err := rows.Err(); err != nil {
	return err
}
return w.Flush()

If you only have column names from database/sql (no ResultSetMetadata), use separate With* options instead:

namer := spanvalue.IndexedUnnamedFieldNamer
names := []string{"id", "name"} // same names passed to WithColumnNames
w, err := writer.NewCSVWriter(
	out,
	writer.WithColumnNames(names),
	writer.WithFormatter(spanvalue.SimpleFormatConfig()),
	writer.WithUnnamedFieldNamer(namer),
)
if err != nil {
	return err
}
defer rows.Close()
for rows.Next() {
	var gcvs []spanner.GenericColumnValue
	// decode the scanned row into gcvs
	if err := w.WriteGCVs(gcvs); err != nil {
		return err
	}
}
if err := rows.Err(); err != nil {
	return err
}
return w.Flush()

Native Spanner client: for *spanner.RowIterator, use writer.WriteRowIterator or writer.RunRowIterator instead of building GCV slices per row.

Metadata pseudo-rows, NextResultSet progression, and stats-only result sets stay in the application (for example spannersh).

ENUM and PROTO in CSV

Delimited output uses SimpleFormatConfig by default. Build cells with gcvctor.EnumValue and gcvctor.ProtoValue, then WriteGCVs (see TestDelimitedWriterWriteGCVsEnumProto in the writer package). For display paths where protobuf descriptors are available, prepend opt-in protofmt plugins to a cloned formatter. These plugins render PROTO values as protobuf text and ENUM values as names; they are display-oriented and do not replace descriptor-free SQL literal output such as FormatProtoAsCast / FormatEnumAsCast. Descriptor loading and compilation stay in the application. If you enable multiline prototext, nested ARRAY/STRUCT cells and delimited-output fields can contain embedded newlines. Delimited, JSONL, and SQL encodings differ after spanvalue formats each column; see writer/README.md. For non-streaming paths, use writer.RowData, writer.FormatDelimitedRow, or writer.FormatJSONLRow directly. Pass the JSON field-name policy explicitly, for example:

line, err := writer.FormatJSONLRow(
	spanvalue.JSONFormatConfig(),
	row,
	spanvalue.IndexedUnnamedFieldNamer,
)

CSV output:

func writeCSV(out io.Writer, rows []*spanner.Row) error {
	w, err := writer.NewCSVWriter(out)
	if err != nil {
		return err
	}
	for _, row := range rows {
		if err := w.WriteRow(row); err != nil {
			return err
		}
	}
	return w.Flush()
}

Quoted TSV uses the same CSV-style writer with a tab delimiter (encoding/csv quoting: embedded tabs, quotes, and newlines in a field are escaped). For CSV output, NewCSVWriter is a thin helper for NewDelimitedWriter(out, writer.Comma). Pass writer.Comma when using the generic delimited constructor for CSV output. Delimiters must be non-zero valid runes other than ", \r, \n, or utf8.RuneError.

func writeTSV(out io.Writer, rows []*spanner.Row) error {
	w, err := writer.NewDelimitedWriter(out, '\t')
	if err != nil {
		return err
	}
	for _, row := range rows {
		if err := w.WriteRow(row); err != nil {
			return err
		}
	}
	return w.Flush()
}

Some CLIs expose a legacy TAB format that joins pre-formatted column strings with \t and does not apply CSV-style quoting. That is not what NewDelimitedWriter(out, '\t') emits. To keep raw tab-separated output while still using spanvalue formatters, implement writer.Writer (or writer.RowIteratorWriter when streaming via writer.WriteRowIterator): format each column, strings.Join(fields, "\t"), then write the line.

JSONL output:

func writeJSONL(out io.Writer, rows []*spanner.Row) error {
	w, err := writer.NewJSONLWriter(out)
	if err != nil {
		return err
	}
	for _, row := range rows {
		if err := w.WriteRow(row); err != nil {
			return err
		}
	}
	return w.Flush()
}

SQL INSERT output uses Spanner GoogleSQL quoting. Use writer.WithSQLInsertKind for INSERT OR IGNORE or INSERT OR UPDATE; see INSERT DML syntax.

func writeInserts(out io.Writer, table string, rows []*spanner.Row) error {
	w, err := writer.NewSQLInsertWriter(out, table)
	if err != nil {
		return err
	}
	for _, row := range rows {
		if err := w.WriteRow(row); err != nil {
			return err
		}
	}
	return w.Flush()
}

Integration tests that exercise the Spanner client with PostgreSQL dialect (TypeAnnotation on query params and row metadata) are maintained in github.com/apstndb/spanpg (integration/pgtypeannotation), not in this repository.

Documentation

Overview

Package spanvalue formats Cloud Spanner data from the Go client (cloud.google.com/go/spanner): cloud.google.com/go/spanner.GenericColumnValue values for individual columns and `*spanner.Row` values for full rows (cloud.google.com/go/spanner.Row), into strings for SQL literals, JSON, Spanner CLI–compatible text, and related styles.

Primary API

Configure output with FormatConfig. Use the constructors LiteralFormatConfig, LiteralFormatConfigWithQuote, LiteralFormatConfigWithSingleQuotedLiterals, LiteralFormatConfigWithOptions, SimpleFormatConfig, SpannerCLICompatibleFormatConfig, and JSONFormatConfig to pick a preset. WithLiteralQuote sets FormatConfig.Literal.Quote on the literal preset (string/bytes delimiter policy for SQL-style literals). After hand-assembling a FormatConfig, call *FormatConfig.Validate at construction time (or immediately after *FormatConfig.Clone and mutation) to catch nil callbacks or an empty FormatConfig.NullString before the first *FormatConfig.FormatRow call. FormatConfigWithoutScalarPlugins and edits to FormatConfig.FormatComplexPlugins can remove preset scalar plugins; re-validate after such changes. Custom scalar plugins in FormatConfig.FormatComplexPlugins are not detected by Validate—keep FormatConfig.FormatNullable non-nil when using them. Package writer does not call Validate on [writer.WithFormatter] configs; validate hand-built formatters before passing them to writers. Scalar plugins (FormatSimpleValue, FormatLiteralValue, FormatSpannerCLIValue, FormatJSONSimpleValue) format GenericColumnValue directly without Decode; remove them with FormatConfigWithoutScalarPlugins or from FormatConfig.FormatComplexPlugins to use Decode + FormatConfig.FormatNullable (set FormatNullable on the clone; nil returns ErrFormatNullableRequired). Scalar plugins fall through to that path when FormatConfig.FormatNullable is set. Constructors return a new FormatConfig; call *FormatConfig.Clone or *FormatConfig.WithComplexPlugin (prepends plugins) before mutating a config you may reuse (*FormatConfig.Clone copies FormatConfig.FormatComplexPlugins). For tuple STRUCT with Spanner CLI scalars, clone SpannerCLICompatibleFormatConfig and set FormatTupleStruct (see README). FormatConfig.FormatColumn runs FormatComplexFunc plugins first, then built-in ARRAY, STRUCT, and scalar formatting. Convenience entry points include FormatRowLiteral, FormatColumnLiteral, FormatRowJSONObject, and FormatRowSpannerCLICompatible. Identifier quoting helpers are QuoteIdentifier and QuoteQualifiedIdentifier.

Advanced extension API

Lower-level callbacks and plugin types are intended for custom output formats: FormatArrayFunc, FormatStructFieldFunc, FormatStructParenFunc, FormatComplexFunc, ErrFallthrough, FormatStruct, FormatTupleStruct, TypedStructFormat, and JSONObjectStructFormat. Customize a FormatConfig from a constructor, or FormatConfig.Clone when reusing one. Convenience formatters such as FormatRowSpannerCLICompatible use internal singleton configs; call FormatConfig.FormatRow on your own config instead of those helpers.

To build cloud.google.com/go/spanner.GenericColumnValue values from Go types, see github.com/apstndb/spanvalue/gcvctor. For streaming row export, see github.com/apstndb/spanvalue/writer. For opt-in descriptor-aware PROTO and ENUM display plugins, see github.com/apstndb/spanvalue/protofmt.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	ErrNilRow                     = errors.New("nil row")
	ErrNilStructField             = errors.New("nil struct field descriptor")
	ErrUnknownType                = errors.New("unknown type")
	ErrMismatchedFields           = errors.New("mismatched struct value/field count")
	ErrUnexpectedComplexValueKind = errors.New("unexpected complex value kind")
	ErrEmptyTypeFQN               = errors.New("empty type FQN")
	// ErrNilFormatConfig is returned by [*FormatConfig.Validate] when the receiver is nil.
	ErrNilFormatConfig = errors.New("nil format config")
	// ErrEmptyNullString is returned by [*FormatConfig.Validate] when [FormatConfig.NullString] is empty.
	ErrEmptyNullString = errors.New("empty null string")
	// ErrNilFormatArray is returned by [*FormatConfig.Validate] when [FormatConfig.FormatArray] is nil.
	ErrNilFormatArray = errors.New("nil format array callback")
	// ErrNilFormatStructField is returned by [*FormatConfig.Validate] when
	// [FormatStruct.FormatStructField] is nil.
	ErrNilFormatStructField = errors.New("nil format struct field callback")
	// ErrNilFormatStructParen is returned by [*FormatConfig.Validate] when
	// [FormatStruct.FormatStructParen] is nil.
	ErrNilFormatStructParen = errors.New("nil format struct paren callback")
	// ErrNilFormatComplexPlugin is returned by [*FormatConfig.Validate] when
	// [FormatConfig.FormatComplexPlugins] contains a nil element.
	ErrNilFormatComplexPlugin = errors.New("nil format complex plugin")
	// ErrFormatNullableRequired is returned from the scalar slow path when
	// the FormatNullable field is nil (no Decode-based formatting), and by
	// [*FormatConfig.Validate] when the FormatNullable field is nil and no preset scalar plugin is configured.
	ErrFormatNullableRequired = errors.New("format nullable required")
)
View Source
var ErrFallthrough = errors.New("fallthrough")

Functions

func ColumnNames added in v0.1.10

func ColumnNames(fields []*sppb.StructType_Field, namer UnnamedFieldNamer) ([]string, error)

ColumnNames returns the names of the provided fields. Unnamed fields are kept as empty strings unless a non-nil namer is provided, in which case the namer is used to generate names for unnamed fields. If a non-nil UnnamedFieldNamer returns an empty string or repeatedly returns colliding names such that a unique column name cannot be chosen, ColumnNames returns a non-nil error describing the contract violation. Explicit duplicate field names from the query schema (for example SELECT 1 AS a, 2 AS a) are preserved as-is; only namer-resolution collisions are errors.

func FormatBracketStruct

func FormatBracketStruct(typ *sppb.Type, toplevel bool, fieldStrings []string) (string, error)

func FormatColumnLiteral

func FormatColumnLiteral(value spanner.GenericColumnValue) (string, error)

func FormatColumnSpannerCLICompatible

func FormatColumnSpannerCLICompatible(value spanner.GenericColumnValue) (string, error)

func FormatCompactArray added in v0.1.9

func FormatCompactArray(_ *sppb.Type, _ bool, elemStrings []string) (string, error)

FormatCompactArray formats array elements without spaces between separators. Output: [elem1,elem2,elem3]

func FormatEnumAsCast

func FormatEnumAsCast(formatter Formatter, value spanner.GenericColumnValue, toplevel bool) (string, error)

func FormatJSONSimpleValue added in v0.1.9

func FormatJSONSimpleValue(formatter Formatter, value spanner.GenericColumnValue, _ bool) (string, error)

FormatJSONSimpleValue is a FormatComplexFunc that formats non-ARRAY, non-STRUCT types as valid JSON values. It returns ErrFallthrough for ARRAY and STRUCT so that the built-in handlers format them.

For most types, structpb.Value.MarshalJSON() produces the correct JSON representation (BOOL→true/false, FLOAT→number, STRING→"quoted", NULL→null, NaN/Inf→"NaN"/"Infinity"). Only INT64, ENUM, and JSON (including PostgreSQL PG_JSONB-annotated JSON) columns need special handling:

  • INT64: Spanner encodes as StringValue("42"), MarshalJSON() would produce "42" (quoted), but we want 42 (unquoted number).
  • ENUM: Spanner stores proto enum values as INT64; same handling as INT64.
  • JSON / PG_JSONB: Spanner encodes as StringValue('{"key":"value"}'), MarshalJSON() would produce escaped quoted string, but we want the raw JSON value passed through.

func FormatLiteralValue added in v0.4.2

func FormatLiteralValue(formatter Formatter, value spanner.GenericColumnValue, _ bool) (string, error)

FormatLiteralValue is a FormatComplexFunc for LiteralFormatConfig. It returns ErrFallthrough for ARRAY, STRUCT, PROTO, and ENUM so FormatProtoAsCast and FormatEnumAsCast run first.

func FormatNullableSpannerCLICompatible

func FormatNullableSpannerCLICompatible(value NullableValue) (string, error)

func FormatOptionallyTypedArray

func FormatOptionallyTypedArray(typ *sppb.Type, toplevel bool, elemStrings []string) (string, error)

FormatOptionallyTypedArray formats ARRAY values for SQL literals. It prefixes the bracket list with an ARRAY<...> type annotation only when toplevel is true and the array element type is complex (STRUCT or nested ARRAY), independent of element count. Scalar element arrays at top level are untyped ([], [1, 2], not ARRAY<INT64>[1, 2]). LiteralFormatConfig wires this as FormatConfig.FormatArray.

func FormatProtoAsCast

func FormatProtoAsCast(formatter Formatter, value spanner.GenericColumnValue, toplevel bool) (string, error)

func FormatRowColumns added in v0.1.10

func FormatRowColumns(fc *FormatConfig, columnNames []string, values []spanner.GenericColumnValue) ([]string, error)

FormatRowColumns formats a row represented as column names plus GCV values. The column names are validated for shape compatibility, but the formatted cell values come from the GCVs themselves.

func FormatRowJSONObject added in v0.1.9

func FormatRowJSONObject(fc *FormatConfig, row *spanner.Row, namer UnnamedFieldNamer) (string, error)

FormatRowJSONObject formats a spanner.Row as a single JSON object string using the given FormatConfig for value formatting and column names as keys. The FormatConfig must produce standalone JSON values per column (e.g., JSONFormatConfig()). Using a non-JSON config produces syntactically invalid output. Empty column names (e.g., from expressions without aliases like SELECT 1+1) are assigned names by the provided namer function. If namer is nil, empty names are kept as empty-string JSON keys. Returns an error if the namer returns an empty name, or if repeated name collisions prevent choosing a unique name for a field. Output: {"col1":val1,"col2":val2,...}

func FormatRowJSONObjectFromColumns added in v0.1.10

func FormatRowJSONObjectFromColumns(fc *FormatConfig, columnNames []string, values []spanner.GenericColumnValue, namer UnnamedFieldNamer) (string, error)

FormatRowJSONObjectFromColumns formats a row represented as column names plus GCV values into a JSON object string. The provided FormatConfig must emit standalone JSON values per column (for example, as configured by JSONFormatConfig()), otherwise the assembled object may be syntactically invalid JSON.

func FormatRowLiteral

func FormatRowLiteral(value *spanner.Row) ([]string, error)

func FormatRowSpannerCLICompatible

func FormatRowSpannerCLICompatible(row *spanner.Row) ([]string, error)

func FormatSimpleStructField

func FormatSimpleStructField(fc *FormatConfig, field *sppb.StructType_Field, value *structpb.Value) (string, error)

func FormatSimpleValue added in v0.4.2

func FormatSimpleValue(formatter Formatter, value spanner.GenericColumnValue, _ bool) (string, error)

FormatSimpleValue is a FormatComplexFunc that formats non-ARRAY, non-STRUCT scalars for SimpleFormatConfig without constructing a NullableValue. It returns ErrFallthrough for ARRAY, STRUCT, and type codes outside [isScalarFastPathTypeCode], when FormatConfig.FormatNullable is not the preset default, or for types handled by earlier plugins. NUMERIC uses the string wire payload as-is; canonical wire is the GCV constructor's responsibility (see github.com/apstndb/spanvalue/gcvctor.StringBasedValueFromCode).

func FormatSpannerCLIValue added in v0.4.2

func FormatSpannerCLIValue(formatter Formatter, value spanner.GenericColumnValue, _ bool) (string, error)

FormatSpannerCLIValue is a FormatComplexFunc for SpannerCLICompatibleFormatConfig.

func FormatTupleStruct

func FormatTupleStruct(typ *sppb.Type, toplevel bool, fieldStrings []string) (string, error)

FormatTupleStruct renders STRUCT values with parentheses, for example (1, east). SimpleFormatConfig uses it by default. To combine tuple STRUCT with Spanner CLI scalars, see SpannerCLICompatibleFormatConfig and the README tuple STRUCT example.

func FormatTypelessStructField

func FormatTypelessStructField(fc *FormatConfig, field *sppb.StructType_Field, value *structpb.Value) (string, error)

func FormatUntypedArray

func FormatUntypedArray(_ *sppb.Type, _ bool, elemStrings []string) (string, error)

func IndexedUnnamedFieldNamer added in v0.1.9

func IndexedUnnamedFieldNamer(index int) string

IndexedUnnamedFieldNamer produces names like "_0", "_1", etc. The underscore prefix minimizes collision with user-defined names. Suitable for row columns (e.g., SELECT 1+1 produces "_0").

func IsNull added in v0.1.10

func IsNull(gcv spanner.GenericColumnValue) bool

IsNull reports whether gcv represents a NULL value. A nil gcv.Value is treated as NULL.

func QuoteIdentifier added in v0.3.0

func QuoteIdentifier(dialect databasepb.DatabaseDialect, name string) string

QuoteIdentifier quotes a single identifier for dialect. It does not validate the identifier; for example, an empty string becomes an empty quoted identifier. DATABASE_DIALECT_UNSPECIFIED follows the Spanner default and uses GoogleSQL quoting.

func QuoteQualifiedIdentifier added in v0.3.0

func QuoteQualifiedIdentifier(dialect databasepb.DatabaseDialect, name string) string

QuoteQualifiedIdentifier quotes each segment of a dotted identifier path for dialect. It does not validate the path; callers that reject empty segments must do so before calling it.

Types

type FormatArrayFunc

type FormatArrayFunc func(typ *sppb.Type, toplevel bool, elemStrings []string) (string, error)

type FormatComplexFunc

type FormatComplexFunc = func(formatter Formatter, value spanner.GenericColumnValue, toplevel bool) (string, error)

FormatComplexFunc is a function to format spanner.GenericColumnValue. If it returns ErrFallthrough, value will pass through to next step.

type FormatConfig

type FormatConfig struct {
	NullString           string
	FormatArray          FormatArrayFunc
	FormatStruct         FormatStruct
	FormatComplexPlugins []FormatComplexFunc
	FormatNullable       FormatNullableFunc
	// Literal holds options for the literal preset only ([LiteralFormatOptions]).
	// Quote is read when FormatNullable is the preset formatNullableValueLiteral (including the
	// formatSimpleColumn slow-path intercept) and by literal preset complex plugins such as
	// [FormatLiteralValue] and [FormatProtoAsCast]. Custom FormatNullable callbacks do not
	// consult this field. Other presets leave Literal at the zero value. Quote zero value is
	// legacy suitableQuote behavior (QuoteLegacy + PreferredDoubleQuote). Invalid enum values
	// are normalized when literal options are applied and again when Quote is read at format
	// time. Escaping uses GoogleSQL backslash rules; not PostgreSQL (#126).
	Literal LiteralFormatOptions
}

FormatConfig controls how Spanner values are formatted. Preset constructors such as LiteralFormatConfig return a fresh instance with non-nil callbacks for the value kinds they support. *FormatConfig.FormatColumn calls FormatArray, FormatStruct, and FormatNullable directly; a nil FormatNullable returns ErrFormatNullableRequired on the scalar slow path unless a FormatComplexFunc plugin handles the value first. Nil FormatArray or FormatStruct still panic when invoked.

Nil field behavior:

Use *FormatConfig.Clone or *FormatConfig.WithComplexPlugin (prepends plugins) to customize a preset without mutating shared instances. Call *FormatConfig.Validate after hand-assembling a config to fail fast on nil callbacks or an empty FormatConfig.NullString.

func FormatConfigWithoutScalarPlugins added in v0.4.2

func FormatConfigWithoutScalarPlugins(fc *FormatConfig) *FormatConfig

FormatConfigWithoutScalarPlugins returns a clone of fc with preset scalar fast-path plugins removed so scalars use Decode and FormatConfig.FormatNullable. Set FormatNullable on the clone before formatting non-NULL scalars; nil FormatNullable returns ErrFormatNullableRequired.

func JSONFormatConfig added in v0.1.9

func JSONFormatConfig() *FormatConfig

JSONFormatConfig returns a new FormatConfig that produces valid JSON value strings for each Spanner value. Each call returns a fresh instance that the caller may customize.

Each formatted string is a standalone JSON value:

  • NULL → null
  • BOOL → true / false
  • INT64 → 42 (unquoted number)
  • FLOAT32/FLOAT64 → 3.14 (NaN/Inf as quoted strings)
  • ENUM → 42 (unquoted number, Spanner stores proto enum values as INT64)
  • STRING, BYTES, TIMESTAMP, DATE, NUMERIC, PROTO, INTERVAL, UUID → "quoted string"
  • JSON column → raw JSON value (passed through)
  • ARRAY → [elem1,elem2,...]
  • STRUCT → {"field1":val1,"field2":val2,...}

func LiteralFormatConfig

func LiteralFormatConfig() *FormatConfig

LiteralFormatConfig returns a new FormatConfig that produces parseable SQL literal expressions with type annotations. ARRAY values use FormatOptionallyTypedArray: top-level arrays with scalar elements omit the ARRAY<...> prefix (empty or not); arrays of STRUCT or nested ARRAY include it when toplevel is true (empty or not).

func LiteralFormatConfigWithOptions added in v0.5.1

func LiteralFormatConfigWithOptions(opts ...LiteralOption) *FormatConfig

LiteralFormatConfigWithOptions returns a copy of LiteralFormatConfig with the given options applied.

func LiteralFormatConfigWithQuote added in v0.5.1

func LiteralFormatConfigWithQuote(cfg LiteralQuoteConfig) *FormatConfig

LiteralFormatConfigWithQuote returns a copy of LiteralFormatConfig with the given quote settings.

func LiteralFormatConfigWithSingleQuotedLiterals added in v0.5.1

func LiteralFormatConfigWithSingleQuotedLiterals() *FormatConfig

LiteralFormatConfigWithSingleQuotedLiterals returns a literal preset that always single-quotes string and bytes literals (SQL INSERT style).

Example
fc := LiteralFormatConfigWithSingleQuotedLiterals()
date, _ := fc.FormatToplevelColumn(gcvctor.DateValue(civil.Date{Year: 2014, Month: 9, Day: 27}))
str, _ := fc.FormatToplevelColumn(gcvctor.StringValue("it's fine"))
fmt.Println(date)
fmt.Println(str)
Output:
DATE '2014-09-27'
'it\'s fine'

func SimpleFormatConfig

func SimpleFormatConfig() *FormatConfig

SimpleFormatConfig returns a new FormatConfig that produces human-readable output using client library conventions.

func SpannerCLICompatibleFormatConfig

func SpannerCLICompatibleFormatConfig() *FormatConfig

SpannerCLICompatibleFormatConfig returns a new FormatConfig that matches the output format of the official spanner-cli tool (bracket-style STRUCT fields in arrays, for example [[1, east]] for `ARRAY<STRUCT<...>>`).

Tuple-style STRUCT parentheses such as [(1, east)] are not spanner-cli output. To keep Spanner CLI scalar formatting but render STRUCT with FormatTupleStruct, clone and customize (constructors return a new config; FormatConfig.Clone copies FormatConfig.FormatComplexPlugins before you mutate):

fc := SpannerCLICompatibleFormatConfig().Clone()
fc.FormatStruct.FormatStructParen = FormatTupleStruct

See the repository README for a tuple STRUCT example. Application-specific presets (for example spanner-mycli table modes) should compose FormatConfig in the caller rather than adding new constructors here.

Example (TupleStruct)

Tuple-style STRUCT in `ARRAY<STRUCT<...>>` while keeping Spanner CLI scalar formatting.

package main

import (
	"fmt"

	"cloud.google.com/go/spanner"

	"github.com/apstndb/spanvalue"
	"github.com/apstndb/spanvalue/gcvctor"
)

func main() {
	fc := spanvalue.SpannerCLICompatibleFormatConfig().Clone()
	fc.FormatStruct.FormatStructParen = spanvalue.FormatTupleStruct

	structElem, err := gcvctor.StructValueOf(
		[]string{"id", "region"},
		[]spanner.GenericColumnValue{gcvctor.Int64Value(1), gcvctor.StringValue("east")},
	)
	if err != nil {
		panic(err)
	}
	arrayOfStruct, err := gcvctor.ArrayValue(structElem)
	if err != nil {
		panic(err)
	}

	out, err := fc.FormatToplevelColumn(arrayOfStruct)
	if err != nil {
		panic(err)
	}
	fmt.Println(out)
}
Output:
[(1, east)]

func (*FormatConfig) Clone added in v0.3.2

func (fc *FormatConfig) Clone() *FormatConfig

Clone returns a shallow copy of fc with a copied FormatComplexPlugins slice. The returned config is independent for field assignment and plugin list mutation; callback values themselves are shared with the source. Clone returns nil when fc is nil.

func (*FormatConfig) FormatColumn

func (fc *FormatConfig) FormatColumn(value spanner.GenericColumnValue, toplevel bool) (string, error)

func (*FormatConfig) FormatRow

func (fc *FormatConfig) FormatRow(row *spanner.Row) ([]string, error)

func (*FormatConfig) FormatToplevelColumn

func (fc *FormatConfig) FormatToplevelColumn(value spanner.GenericColumnValue) (string, error)

func (*FormatConfig) GetNullString added in v0.1.10

func (fc *FormatConfig) GetNullString() string

func (*FormatConfig) Validate added in v0.6.0

func (fc *FormatConfig) Validate() error

Validate reports invalid hand-built FormatConfig values. Preset constructors return configs that pass *FormatConfig.Validate. Nil fc returns ErrNilFormatConfig.

Static checks: non-empty NullString (empty is rejected so NULL output is explicit, not ambiguous with an empty STRING); non-nil FormatArray and FormatStruct callback fields; non-nil elements in FormatComplexPlugins. The FormatNullable field may be nil when a preset scalar plugin is present in FormatComplexPlugins; when scalar plugins are absent, a nil FormatNullable field fails validation because non-NULL scalars have no formatter (runtime behavior is defined in #163). Validate does not prove that plugin-only configs format every type. Only preset scalar plugins satisfy the FormatNullable exemption; custom scalar plugins in FormatComplexPlugins are not detected, so keep FormatNullable non-nil to pass Validate when using custom plugins.

func (*FormatConfig) WithComplexPlugin added in v0.7.0

func (fc *FormatConfig) WithComplexPlugin(plugin FormatComplexFunc) *FormatConfig

WithComplexPlugin returns a clone of fc with plugin prepended to FormatComplexPlugins so it runs before existing plugins (including preset scalar plugins). This matches the protofmt pattern of prepending descriptor-aware plugins before preset defaults. The original config, including shared preset singletons, is not mutated. Chain further calls on the returned config for additional plugins (each prepends, so the most recent call runs first). Nil fc returns nil. A nil plugin panics so a mistaken nil in a chain fails at the call site instead of collapsing the chain to nil.

type FormatNullableFunc

type FormatNullableFunc = func(value NullableValue) (string, error)

type FormatStruct

type FormatStruct struct {
	FormatStructField FormatStructFieldFunc
	FormatStructParen FormatStructParenFunc
}

func TypedStructFormat added in v0.4.0

func TypedStructFormat() FormatStruct

TypedStructFormat returns a FormatStruct that formats STRUCT values with typed field names in literal style.

type FormatStructFieldFunc

type FormatStructFieldFunc func(fc *FormatConfig, field *sppb.StructType_Field, value *structpb.Value) (string, error)

type FormatStructParenFunc

type FormatStructParenFunc func(typ *sppb.Type, toplevel bool, fieldStrings []string) (string, error)

func JSONObjectStructFormat added in v0.4.0

func JSONObjectStructFormat(namer UnnamedFieldNamer) FormatStructParenFunc

JSONObjectStructFormat returns a FormatStructParenFunc that formats struct fields as a JSON object. When namer is nil, unnamed struct fields produce empty-string keys, matching Spanner's own representation.

func NewJSONObjectStructFormatter added in v0.1.9

func NewJSONObjectStructFormatter(namer UnnamedFieldNamer) FormatStructParenFunc

NewJSONObjectStructFormatter creates a FormatStructParenFunc that formats struct fields as a JSON object with field names as keys. Unnamed fields are assigned names by the provided namer function. If namer is nil, unnamed fields keep empty-string keys (which produces duplicate keys — valid per RFC 8259 but may cause issues with parsers that deduplicate keys). Returns an error if the namer returns an empty name, or if repeated name collisions prevent choosing a unique name for a field. Output: {"field1":val1,"field2":val2,...}

type Formatter

type Formatter interface {
	FormatColumn(value spanner.GenericColumnValue, toplevel bool) (string, error)
	GetNullString() string
}

type LiteralFormatOptions added in v0.5.1

type LiteralFormatOptions struct {
	// Quote selects the outer delimiter policy for string and bytes SQL-style literals.
	Quote LiteralQuoteConfig
}

LiteralFormatOptions holds settings that apply only to the literal preset (LiteralFormatConfig and clones). Other presets ignore this field. A future major release may move literal-specific configuration off FormatConfig entirely; callers should set options via literal constructors or WithLiteralQuote rather than assuming a stable nested layout.

type LiteralOption added in v0.5.1

type LiteralOption interface {
	// contains filtered or unexported methods
}

LiteralOption configures a literal preset returned by LiteralFormatConfigWithOptions.

func WithLiteralQuote added in v0.5.1

func WithLiteralQuote(cfg LiteralQuoteConfig) LiteralOption

WithLiteralQuote sets quote policy on a literal preset built with LiteralFormatConfigWithOptions.

type LiteralQuoteConfig added in v0.5.1

type LiteralQuoteConfig struct {
	Strategy       QuoteStrategy
	PreferredQuote PreferredQuote
}

LiteralQuoteConfig configures string and bytes literal quoting for the literal preset. The zero value is legacy adaptive quoting. Invalid enum values are normalized per axis.

type NullBytes

type NullBytes []byte

func (NullBytes) IsNull

func (n NullBytes) IsNull() bool

func (NullBytes) String

func (n NullBytes) String() string

type NullableValue

type NullableValue interface {
	spanner.NullableValue
	fmt.Stringer
}

type PreferredQuote added in v0.5.1

type PreferredQuote uint8

PreferredQuote is the default delimiter for QuoteLegacy (with opposite-delimiter escape when the payload contains only that quote character), the fixed delimiter for QuoteAlways, and the tie-breaker for QuoteMinEscape.

const (
	// PreferredDoubleQuote uses double quotes as the outer delimiter.
	PreferredDoubleQuote PreferredQuote = iota
	// PreferredSingleQuote uses single quotes as the outer delimiter.
	PreferredSingleQuote
)

func (PreferredQuote) String added in v0.5.1

func (p PreferredQuote) String() string

type QuoteStrategy added in v0.5.1

type QuoteStrategy uint8

QuoteStrategy selects how the outer string-literal delimiter is chosen for the literal preset.

const (
	// QuoteLegacy uses PreferredQuote when the payload needs no opposite-delimiter escape.
	// When the payload contains only the preferred quote character, the opposite delimiter is used.
	// PreferredDoubleQuote (the zero value) matches historical suitableQuote byte-for-byte.
	// When both quote characters appear, Legacy uses presence rules (any opposite quote keeps
	// the preferred delimiter); [QuoteMinEscape] instead compares quote-character counts.
	QuoteLegacy QuoteStrategy = iota
	// QuoteAlways uses PreferredQuote for every string and bytes literal.
	QuoteAlways
	// QuoteMinEscape picks the delimiter whose quote character occurs less often in the payload.
	// On a tie, PreferredQuote wins. Only quote-character counts matter; other escapes are delimiter-independent.
	// With PreferredSingleQuote it often matches [QuoteLegacy], but when both delimiters appear
	// MinEscape compares counts (e.g. a”b"c stays single-quoted under Legacy+Single but uses
	// double under MinEscape+Single because one double escape beats two single escapes).
	QuoteMinEscape
)

func (QuoteStrategy) String added in v0.5.1

func (s QuoteStrategy) String() string

type UnnamedFieldNamer added in v0.1.9

type UnnamedFieldNamer func(index int) string

UnnamedFieldNamer generates a name for an unnamed field or column. The index argument is a monotonically increasing counter (not necessarily the field's positional index) that may skip values due to collision avoidance. It must return distinct non-empty names for distinct indices. Functions that accept UnnamedFieldNamer (such as NewJSONObjectStructFormatter and FormatRowJSONObject) return an error if the namer violates this contract. Pass nil instead of a namer to keep unnamed fields as empty-string keys.

Directories

Path Synopsis
Package dbsqlrows is experimental: APIs may change before a stable release.
Package dbsqlrows is experimental: APIs may change before a stable release.
Package gcvctor constructs cloud.google.com/go/spanner.GenericColumnValue values from Go values and explicit cloud.google.com/go/spanner/apiv1/spannerpb.Type metadata, using github.com/apstndb/spantype/typector for type shapes.
Package gcvctor constructs cloud.google.com/go/spanner.GenericColumnValue values from Go values and explicit cloud.google.com/go/spanner/apiv1/spannerpb.Type metadata, using github.com/apstndb/spantype/typector for type shapes.
Package protofmt provides descriptor-aware PROTO and ENUM formatting plugins for spanvalue format configs.
Package protofmt provides descriptor-aware PROTO and ENUM formatting plugins for spanvalue format configs.
Package writer streams Spanner query results to delimited text, JSONL, or SQL INSERT using github.com/apstndb/spanvalue formatters.
Package writer streams Spanner query results to delimited text, JSONL, or SQL INSERT using github.com/apstndb/spanvalue formatters.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL