Bug description
Binding an NTNT String to a PostgreSQL parameter whose inferred type is timestamptz sends UTF-8 text bytes using the TIMESTAMPTZ binary type OID. PostgreSQL rejects the bind before executing the query:
ERROR: incorrect binary data format in bind parameter 1
CONTEXT: unnamed portal parameter $1
This was observed in NTNT 0.5.2 at source revision 04db475d339851970d2c872c333021a4b10d11f3. The current main implementation at 79c61dd98b0f10e3f6c1bce4f1d6e4df2343a21f appears to retain the same behavior.
Minimal reproduction
With a reachable PostgreSQL database:
import { connect, query_one } from "std/db/postgres"
let db = unwrap(connect("postgres://user:password@localhost/example"))
let row = unwrap(query_one(
db,
"SELECT $1::timestamptz::text AS value",
["2026-07-29T12:54:21.000000Z"]
))
print(row)
Actual behavior
query_one returns an error originating from PostgreSQL:
incorrect binary data format in bind parameter 1
A production occurrence showed the same failure with a cursor predicate:
AND (
$3::timestamptz IS NULL
OR (received_at, probe_run_id) > ($3::timestamptz, $4::uuid)
)
The UUID string parameter was coerced correctly; the timestamptz string parameter was not.
Expected behavior
Preferably, NTNT should parse a String into the target PostgreSQL timestamp type and encode the correct binary representation, consistent with the existing string coercions for integer, float, boolean, JSON, and UUID parameters.
If timestamp coercion is intentionally unsupported, the client should reject the bind with a clear NTNT error rather than sending malformed binary data to PostgreSQL.
Confirmed workaround
Force PostgreSQL to infer the parameter as text, then cast the resulting text value:
SELECT $1::text::timestamptz
For optional cursor parameters, this works:
NULLIF($1::text, '')::timestamptz
The same live query that failed with $1::timestamptz succeeded after changing it to $1::text::timestamptz.
Likely source
In src/stdlib/postgres.rs, SqlParam::String handles several target types explicitly, then falls through to:
SqlParam::accepts returns true for every type. For TIMESTAMPTZ, the fallback appears to write string bytes while the prepared parameter still carries the timestamp binary OID.
A likely fix is to add explicit coercion for DATE, TIME, TIMESTAMP, and TIMESTAMPTZ using the corresponding chrono types, plus PostgreSQL integration tests for valid and invalid strings.
Environment
- NTNT:
0.5.2
- NTNT source revision:
04db475d339851970d2c872c333021a4b10d11f3
- PostgreSQL: 18
- Runtime: Debian Bookworm container on Linux
Bug description
Binding an NTNT
Stringto a PostgreSQL parameter whose inferred type istimestamptzsends UTF-8 text bytes using theTIMESTAMPTZbinary type OID. PostgreSQL rejects the bind before executing the query:This was observed in NTNT
0.5.2at source revision04db475d339851970d2c872c333021a4b10d11f3. The currentmainimplementation at79c61dd98b0f10e3f6c1bce4f1d6e4df2343a21fappears to retain the same behavior.Minimal reproduction
With a reachable PostgreSQL database:
Actual behavior
query_onereturns an error originating from PostgreSQL:A production occurrence showed the same failure with a cursor predicate:
The UUID string parameter was coerced correctly; the
timestamptzstring parameter was not.Expected behavior
Preferably, NTNT should parse a
Stringinto the target PostgreSQL timestamp type and encode the correct binary representation, consistent with the existing string coercions for integer, float, boolean, JSON, and UUID parameters.If timestamp coercion is intentionally unsupported, the client should reject the bind with a clear NTNT error rather than sending malformed binary data to PostgreSQL.
Confirmed workaround
Force PostgreSQL to infer the parameter as text, then cast the resulting text value:
For optional cursor parameters, this works:
The same live query that failed with
$1::timestamptzsucceeded after changing it to$1::text::timestamptz.Likely source
In
src/stdlib/postgres.rs,SqlParam::Stringhandles several target types explicitly, then falls through to:SqlParam::acceptsreturnstruefor every type. ForTIMESTAMPTZ, the fallback appears to write string bytes while the prepared parameter still carries the timestamp binary OID.A likely fix is to add explicit coercion for
DATE,TIME,TIMESTAMP, andTIMESTAMPTZusing the corresponding chrono types, plus PostgreSQL integration tests for valid and invalid strings.Environment
0.5.204db475d339851970d2c872c333021a4b10d11f3