Conversation
Co-authored-by: mickamy <11856337+mickamy@users.noreply.github.com>
date/time field value out of range for binary-encoded timestamp args
|
@copilot fix lint errors |
Co-authored-by: mickamy <11856337+mickamy@users.noreply.github.com>
Fixed in 67deaca. Two lint issues:
|
There was a problem hiding this comment.
Pull request overview
Fixes a PostgreSQL EXPLAIN ANALYZE failure (SQLSTATE 22008) when replaying captured queries whose timestamp arguments were originally sent in binary format and later re-sent as plain strings.
Changes:
- Convert captured string args into
[]anyviabuildAnyArgs, attempting to re-type certain timestamp-cast parameters astime.Time. - Add parsing helpers for detecting timestamp-cast parameters and decoding PostgreSQL’s internal (2000-01-01-based) microsecond timestamp representation.
- Add unit tests covering timestamp param detection, timestamp decoding, and mixed-argument behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| explain/explain.go | Introduces arg re-typing logic for timestamp-cast parameters before running EXPLAIN. |
| explain/build_args_test.go | Adds unit tests for the new arg-building and parsing helpers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func buildAnyArgs(query string, args []string) []any { | ||
| tsParams := parseTimestampParams(query) | ||
| anyArgs := make([]any, len(args)) | ||
| for i, a := range args { | ||
| if tsParams[i+1] { | ||
| if t, ok := parsePGTimestamp(a); ok { |
There was a problem hiding this comment.
buildAnyArgs() only converts integer args when the query text contains an explicit $N::TIMESTAMP... cast. However, in the Postgres proxy the captured query used for EXPLAIN is the raw Parse query (q := c.lastParse) and typically does not include explicit casts; parameter types are usually inferred from context (e.g., created_at > $1). In those cases this conversion will never trigger and EXPLAIN can still fail with SQLSTATE 22008 for binary-encoded timestamptz args. Consider using protocol type information instead of query-text regex (e.g., capture Parse.ParameterOIDs and/or per-parameter format codes in the event / ExplainRequest) so timestamp parameters can be detected even when there is no ::TIMESTAMP in the SQL.
There was a problem hiding this comment.
Fixed in ad49ce2 by addressing the issue at the protocol level instead of relying on query-text regex.
Changes in proxy/postgres/conn.go:
handleParsenow capturesm.ParameterOIDsintolastParamOIDs(andpreparedStmtOIDsfor named statements)handleBindlooks up OIDs per parameter and passes them todecodeBinaryParamdecodeBinaryParamnow detects timestamp OIDs (1114 =timestamp, 1184 =timestamptz) and decodes the 8-byte binary value usingdecodePGTimestampMicros→ stores an RFC3339Nano string (e.g."2026-02-27T14:10:30.505628Z") that PostgreSQL can parse back correctly in both explicit-cast and implicit-cast contexts
The existing buildAnyArgs regex in explain.go is kept as a fallback for drivers that don't send OIDs but have explicit $N::TIMESTAMP casts.
…39 strings Co-authored-by: mickamy <11856337+mickamy@users.noreply.github.com>
EXPLAIN ANALYZEcrashes withSQLSTATE 22008when a query arg was originally sent as a binary-formatTIMESTAMP WITH TIME ZONE. The proxy captures binary timestamp parameters as raw int64 strings (µs since 2000-01-01 UTC, e.g.825505830505628). When passed back to PostgreSQL as a plain Gostringin a parameterized query, PG's text parser rejects it — it can't parse an integer as a timestamp literal.Changes
proxy/postgres/conn.go— Fix the root cause at the protocol level:handleParsenow capturesm.ParameterOIDsintolastParamOIDs(andpreparedStmtOIDsfor named prepared statements)handleBindlooks up the OID for each parameter position and passes it todecodeBinaryParamdecodeBinaryParamnow checks if the OID istimestamp(1114) ortimestamptz(1184): if so, decodes the 8-byte binary value as PostgreSQL microseconds since 2000-01-01 UTC and formats it as an RFC3339Nano string (e.g."2026-02-27T14:10:30.505628Z") — a value PostgreSQL can parse back correctly in any parameterized query context, with or without an explicit::TIMESTAMPcastexplain/explain.go— Retains thebuildAnyArgs()/parseTimestampParams()/parsePGTimestamp()helpers as a fallback for drivers that do not sendParameterOIDsin the Parse message but do include explicit$N::TIMESTAMP...casts in the query textproxy/postgres/conn_test.goandproxy/postgres/export_test.go— Unit tests fordecodePGTimestampMicroscovering zero, large positive, negative, and fractional-negative microsecond valuesOriginal prompt
fails withdate/time field value out of range` when args contain Unix timestamps in milliseconds/microseconds #21💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.