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 ¶
- Variables
- func EncodeOptions() []spancodec.EncodeOption
- func FormatColumnPostgreSQLLiteral(value spanner.GenericColumnValue) (string, error)
- func FormatColumnSimple(gcv spanner.GenericColumnValue) (string, error)
- func FormatPostgreSQLType(typ *sppb.Type) string
- func FormatRowPostgreSQLLiteral(value *spanner.Row) ([]string, error)
- func InsertStatement(table string, columns []string, values []any) (spanner.Statement, error)
- func PositionalParams(values []any) (map[string]any, error)
- func PostgreSQLLiteralFormatConfig() *spanvalue.FormatConfig
- func PostgreSQLPlaceholder(n int) (sql string, ok bool)
- func StatementParamKey(n int) (key string, ok bool)
Constants ¶
This section is empty.
Variables ¶
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):
- math/big.Rat and *math/big.Rat (nil pointer → typed NULL PG_NUMERIC) → github.com/apstndb/spanvalue/gcvctor.PGNumericValue
- cloud.google.com/go/spanner.NullNumeric → PGNumericValue when Valid, typed NULL PG_NUMERIC otherwise
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. Because these registrations override the client mirror, github.com/apstndb/spancodec.WithLossOfPrecisionHandling does not apply to them; pass values needing more fractional digits (or NaN) as cloud.google.com/go/spanner.PGNumeric wire strings instead.
- cloud.google.com/go/spanner.NullJSON → github.com/apstndb/spanvalue/gcvctor.PGJSONBValue of Value when Valid, typed NULL PG_JSONB otherwise. Like the client, Value is always marshaled: a Go string Value becomes a quoted JSON string on the wire; pass wire-format JSON text as encoding/json.RawMessage.
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 paired WithGoType 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. The slice types themselves are additionally registered with WithGoType: static inference resolves an exact Go type (the client supports []big.Rat etc. natively) before falling back to a registered element type, so without the slice registrations TypeFor[[]T] would report un-annotated ARRAY element types.
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 ¶
FormatPostgreSQLType renders a sppb.Type using PostgreSQL-dialect spellings from https://docs.cloud.google.com/spanner/docs/reference/postgresql/data-types (supported types table: bool, bytea, date, float4, float8, bigint, interval, jsonb, numeric, timestamptz, text, uuid, oid, and array declarations). sppb.TypeCode_JSON is formatted as "jsonb" only when the type carries sppb.TypeAnnotationCode_PG_JSONB; otherwise it is formatted as "json" (non-PG JSON shapes are not defined in the PostgreSQL data-types table today, but the label keeps the distinction from PG_JSONB wire types).
sppb.TypeCode_PROTO and sppb.TypeCode_ENUM are not PostgreSQL-interface types: the PostgreSQL dialect does not support Protocol Buffers, so column values of these types are not returned in normal use. Dialect documentation may mention BYTEA or TEXT with CHECK constraints as migration substitutes for GoogleSQL; those are not the same as PROTO or ENUM on the wire. This function therefore formats them as "proto" and "enum"—not as "bytea" or "text"—so wire types are never mislabeled if they appear in metadata or in a future extension.
sppb.TypeCode_STRUCT does not appear in PostgreSQL-dialect metadata in practice today, but the implementation includes provisional formatting so callers are not left with a gap if STRUCT shows up on the wire later. The current output uses GoogleSQL STRUCT<…> declaration syntax (named fields: STRUCT<name type, …>; unnamed: STRUCT<type, …>), with field types spelled using the PostgreSQL data-type names above. When reviewing or updating this behavior, read Working with STRUCT objects from https://docs.cloud.google.com/spanner/docs/structs (e.g. dkcli get docs.cloud.google.com/spanner/docs/structs). The formatting for STRUCT may change once PostgreSQL-dialect support or official guidance for representing composite types is defined (this is not a stability guarantee for the STRUCT branch).
func FormatRowPostgreSQLLiteral ¶ added in v0.2.0
FormatRowPostgreSQLLiteral formats a row using PostgreSQLLiteralFormatConfig.
func InsertStatement ¶ added in v0.2.0
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
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 ¶
PostgreSQLPlaceholder returns the SQL text for the n-th bind placeholder in PostgreSQL dialect ($n, 1-based), e.g. 1 → "$1".
func StatementParamKey ¶
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.