spanpg

package module
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 18 Imported by: 0

README

spanpg

Go Reference

Experimental, optional layer for PostgreSQL dialect ergonomics around Cloud Spanner — built on top of:

The module API is unstable until declared otherwise.

API (experimental)

  • FormatPostgreSQLType — render a google.spanner.v1.Type using PostgreSQL-dialect spellings (including STRUCT<…> and json / jsonb by annotation).
  • StatementParamKey / PostgreSQLPlaceholder — pair $n placeholders with spanner.Statement.Params keys (p1, p2, …), matching the client convention (see integration/pgtypeannotation).
  • PostgreSQLLiteralFormatConfig / FormatColumnPostgreSQLLiteral / FormatRowPostgreSQLLiteral — format GenericColumnValue / *spanner.Row values as PostgreSQL literal SQL and reject Spanner types the PostgreSQL interface does not support.
  • FormatColumnSimple — delegates to spanvalue.SimpleFormatConfig for readable column text.

Status

go.mod pins github.com/apstndb/spanvalue v0.2.1 and github.com/apstndb/spantype v0.3.11. The API remains unstable until declared otherwise.

Integration tests

The nested module integration/pgtypeannotation runs PostgreSQL-dialect Spanner client probes (emulator or real instance). It is not part of root go test ./...; use make test-integration (see Makefile and .github/workflows/go.yml).

Development

go test ./...
go vet ./...

License

MIT — see LICENSE.

Documentation

Overview

Package spanpg provides experimental PostgreSQL-dialect helpers around Cloud Spanner: FormatPostgreSQLType for type strings, placeholder/param-key pairing for PostgreSQL SQL text, PostgreSQL literal formatting presets built on github.com/apstndb/spanvalue, thin bridges to existing spanvalue formatting, EncodeOptions adapting github.com/apstndb/spancodec encoding to the PG_NUMERIC / PG_JSONB type annotations the dialect requires on parameters, and PositionalParams / InsertStatement for binding ordered values to $1..$n placeholders.

Behavioral notes (query parameters use cloud.google.com/go/spanner.PGNumeric / cloud.google.com/go/spanner.PGJsonB; row metadata exposes TypeAnnotation on column types) are covered by the nested module `integration/pgtypeannotation` in this repository (see that directory’s README).

Stable cloud.google.com/go/spanner.GenericColumnValue construction remains in github.com/apstndb/spanvalue/gcvctor; [google.spanner.v1.Type] string rendering remains in github.com/apstndb/spantype. PostgreSQL-dialect spellings for cloud.google.com/go/spanner/apiv1/spannerpb.Type are available via FormatPostgreSQLType (see https://docs.cloud.google.com/spanner/docs/reference/postgresql/data-types and https://docs.cloud.google.com/spanner/docs/reference/dialect-differences).

Index

Constants

This section is empty.

Variables

View Source
var ErrUnsupportedPostgreSQLType = errors.New("unsupported PostgreSQL type")

ErrUnsupportedPostgreSQLType reports a Spanner type that cannot be rendered as executable PostgreSQL-dialect SQL because the interface does not support it.

Functions

func EncodeOptions added in v0.2.0

func EncodeOptions() []spancodec.EncodeOption

EncodeOptions returns github.com/apstndb/spancodec options that adapt encoding to the PostgreSQL dialect: NUMERIC-family and JSON-family Go values produce PG_NUMERIC / PG_JSONB annotated GCVs instead of the GoogleSQL forms. Pass them to github.com/apstndb/spancodec.ValueOf, github.com/apstndb/spancodec.RowTypeFor, github.com/apstndb/spancodec.NewRowEncoder, and friends.

The adaptation is required, not cosmetic: the POSTGRESQL dialect rejects query parameters whose type lacks the annotation. Probe (integration/pgtypeannotation, emulator 1.5.54): binding a GenericColumnValue param with plain code:NUMERIC fails with codes.Unimplemented "Unsupported GoogleSQL Type: NUMERIC", and plain code:JSON fails with "Unsupported GoogleSQL Type: JSON"; the same values with the PG_NUMERIC / PG_JSONB annotation are accepted and the result column metadata echoes the annotation.

Registered mappings (each github.com/apstndb/spancodec.WithValueEncoder is paired with github.com/apstndb/spancodec.WithGoType so static inference — TypeFor, RowTypeFor, RowEncoder.RowType / ResultSetMetadata — carries the annotations too):

PGNumericValue formats the wire string with the client's canonical cloud.google.com/go/spanner.NumericString (9 fractional digits, silently rounding) even though PG numeric accepts a wider value space — matching the client mirror's NUMERIC scale. Because these registrations override the client mirror, github.com/apstndb/spancodec.WithLossOfPrecisionHandling does not apply to them. For exact wider-scale values use github.com/apstndb/spanvalue/gcvctor.PGNumericValueExact (spanvalue v0.8.1+) or pass cloud.google.com/go/spanner.PGNumeric wire strings (also the NaN path) directly.

cloud.google.com/go/spanner.PGNumeric and cloud.google.com/go/spanner.PGJsonB already encode with the annotations via the client mirror and are deliberately NOT registered; they pass through unchanged with or without these options.

Hazard model (same as spancodec's): a registration overrides ALL built-in handling for its exact dynamic type only. Named types (e.g. type MyRat big.Rat) and other NUMERIC-capable inputs such as string-based wire values are untouched. Per spancodec's contract a registration for T also applies per element of []T, and the registered Spanner type supplies the ARRAY element type for nil and empty slices, so []big.Rat, []*big.Rat, []spanner.NullNumeric, and []spanner.NullJSON encode as arrays of PG-annotated elements. Since spancodec v0.1.2 static inference also resolves []T from the element registration (apstndb/spancodec#1), so no separate slice-type registrations are needed.

Decoding needs no counterpart: the client (and therefore github.com/apstndb/spancodec.Decode / github.com/apstndb/spancodec.ToStruct) checks only the TypeCode for big.Rat, NullNumeric, and NullJSON destinations, so PG_NUMERIC / PG_JSONB columns decode into them natively (pinned in integration/pgtypeannotation). The one PG-specific decode hazard is the value space, not the type: PG_NUMERIC columns can hold "NaN", which no big.Rat-based destination can represent (the client fails with "unexpected string value"); use cloud.google.com/go/spanner.PGNumeric when NaN is possible.

func FormatColumnPostgreSQLLiteral added in v0.2.0

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

FormatColumnPostgreSQLLiteral formats a top-level column using PostgreSQLLiteralFormatConfig.

func FormatColumnSimple

func FormatColumnSimple(gcv spanner.GenericColumnValue) (string, error)

FormatColumnSimple formats a spanner.GenericColumnValue using github.com/apstndb/spanvalue.SimpleFormatConfig (human-readable scalar and container output).

func FormatPostgreSQLType deprecated

func FormatPostgreSQLType(typ *sppb.Type) string

FormatPostgreSQLType renders a sppb.Type using PostgreSQL-dialect spellings.

Deprecated: the spelling table moved to github.com/apstndb/spantype.FormatTypePostgreSQL (spantype v0.3.12+) so type rendering is available without depending on spanpg; this alias delegates there and will be removed in the next breaking release. The spellings remain pinned against PostgreSQL-dialect Spanner databases by this repository's integration/pgtypeannotation probes.

func FormatRowPostgreSQLLiteral added in v0.2.0

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

FormatRowPostgreSQLLiteral formats a row using PostgreSQLLiteralFormatConfig.

func InsertStatement added in v0.2.0

func InsertStatement(table string, columns []string, values []any) (spanner.Statement, error)

InsertStatement builds INSERT INTO <table> (<cols>) VALUES ($1..$n) with PositionalParams, quoting the table and column identifiers with github.com/apstndb/spanvalue.QuoteIdentifier PostgreSQL rules. It returns an error when len(columns) != len(values) or when columns is empty.

values follow the PositionalParams contract: plain Go values or GenericColumnValue. Generic INSERT fragment helpers belong to spanvalue (apstndb/spanvalue#79); this binds the PostgreSQL-specific $n / p-n pairing.

func PositionalParams added in v0.2.0

func PositionalParams(values []any) (map[string]any, error)

PositionalParams maps ordered values to the p1..pn keys the PostgreSQL interface pairs with $1..$n placeholders (see StatementParamKey). values may be plain Go values (the client encodes them) or cloud.google.com/go/spanner.GenericColumnValue (binding an exact wire Type, e.g. from github.com/apstndb/spancodec.ValueOf with EncodeOptions).

func PostgreSQLLiteralFormatConfig added in v0.2.0

func PostgreSQLLiteralFormatConfig() *spanvalue.FormatConfig

PostgreSQLLiteralFormatConfig returns a new spanvalue.FormatConfig that produces parseable PostgreSQL-dialect literal expressions for scalar values plus ARRAY constructors. It rejects Spanner-specific types that the PostgreSQL interface does not support (for example PROTO, ENUM, and STRUCT) instead of emitting invalid SQL.

NULL values—scalar NULL and NULL arrays alike—render as the bare keyword NULL, not CAST(NULL AS <type>). Bare NULL is valid wherever the surrounding context fixes the type (INSERT column lists, comparisons against typed columns), which is the intended use of this config (for example SQL INSERT export through github.com/apstndb/spanvalue/writer). In context-free positions such as a bare SELECT NULL the backend has no type to infer, so callers needing a typed NULL expression must wrap it themselves (CAST(NULL AS bigint), ...). Both behaviors are pinned by the literal round-trip harness in integration/pgtypeannotation (pgliteral_roundtrip_test.go), which executes every literal form this config emits against a POSTGRESQL-dialect Spanner database.

Known backend canonicalizations (probed by the same harness): timestamptz literals are parsed with microsecond precision—sub-microsecond digits are rounded, and a nanosecond-precision value at the maximum timestamp (9999-12-31T23:59:59.999999999Z) rounds out of range and fails—and jsonb text is normalized (key order, whitespace, unicode escapes).

func PostgreSQLPlaceholder

func PostgreSQLPlaceholder(n int) (sql string, ok bool)

PostgreSQLPlaceholder returns the SQL text for the n-th bind placeholder in PostgreSQL dialect ($n, 1-based), e.g. 1 → "$1".

func StatementParamKey

func StatementParamKey(n int) (key string, ok bool)

StatementParamKey returns the map key used in cloud.google.com/go/spanner.Statement.Params for the PostgreSQL-style placeholder $n where n is 1-based. For example, placeholder $1 uses Params key "p1", $2 uses "p2".

This matches the cloud.google.com/go/spanner client convention used with PostgreSQL dialect SQL (placeholders $1, $2, …). See integration coverage in https://github.com/apstndb/spanvalue/pull/45.

Types

This section is empty.

Jump to

Keyboard shortcuts

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