apache/beam · error

spannerio.Query: specifying timestamp bound for non-batched…

Error message

spannerio.Query: specifying timestamp bound for non-batched reads not currently supported.

What it means

The internal query() path (non-batched reads) panics if queryOptions.TimestampBound was set, because stale-read bounds are only implemented for the batched read path. It is an explicit unsupported-feature guard in spannerio.

Solutions

  1. Enable batching with spannerio.UseBatching(true) as a QueryOptionFn so the timestamp bound is honored via readBatch.
  2. Remove the timestamp bound option if strong/regular reads are acceptable.
  3. Implement a custom DoFn with the Spanner client directly if neither option fits.

Example fix

// before
spannerio.Query(s, db, q, t, spannerio.WithQueryTimestampBound(tb))
// after
spannerio.Query(s, db, q, t, spannerio.UseBatching(true), spannerio.WithQueryTimestampBound(tb))
Defensive patterns

Strategy: validation

Validate before calling

if timestampBound != (spanner.TimestampBound{}) && !batchingEnabled {
    return fmt.Errorf("timestamp bound requires spannerio.UseBatching(true)")
}

Try / catch

defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("spannerio timestamp bound unsupported here: %v", r)
    }
}()

Prevention

When it happens

Trigger: Calling spannerio.Query (or Read) with WithQueryTimestampBound(spanner.StrongRead()) or any TimestampBound while batching is disabled (default, or UseBatching(false)).

Common situations: Users copying batched-read examples and adding timestamp bounds, or enabling staleness reads for consistency tuning without realizing it requires the batching path.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/58bf8c89c1bee7f3. Report an issue: GitHub.

Appendix: source

Thrown at sdks/go/pkg/beam/io/spannerio/read.go:77

func Query(s beam.Scope, db string, q string, t reflect.Type, options ...QueryOptionFn) beam.PCollection {
	queryOptions := newQueryOptions(options...)

	if db == "" {
		panic("no database provided!")
	}

	if queryOptions.Batching {
		return readBatch(s, db, q, t, queryOptions)
	} else {
		return query(s, db, q, t, queryOptions)
	}
}

func query(s beam.Scope, db string, query string, t reflect.Type, options queryOptions) beam.PCollection {
	s = s.Scope("spanner.Query")

	if options.TimestampBound != (spanner.TimestampBound{}) {
		panic("spannerio.Query: specifying timestamp bound for non-batched reads not currently supported.")
	}

	imp := beam.Impulse(s)

	return beam.ParDo(s, newQueryFn(db, query, t, options), imp, beam.TypeDefinition{Var: beam.XType, T: t})
}

type queryFn struct {
	spannerFn
	Query   string           `json:"query"`   // Table is the table identifier.
	Type    beam.EncodedType `json:"type"`    // Type is the encoded schema type.
	Options queryOptions     `json:"options"` // Options specifies additional query execution options.
}

func newQueryFn(
	db string,
	query string,
	t reflect.Type,

View on GitHub (pinned to 12126d8942)