-
-
Notifications
You must be signed in to change notification settings - Fork 46
Description
Description
When using the PostgreSQL extended query protocol, DoltgreSQL sends TIMESTAMPTZ and DATE column values as text strings instead of the expected binary format. This breaks typed PostgreSQL clients that expect the standard binary encoding.
TIMESTAMPTZ: Expected 8-byte binary (i64, microseconds since 2000-01-01 UTC), but DoltgreSQL sends a 29-byte text string like 2026-01-15 10:30:00.076264+00.
DATE: Expected 4-byte binary (i32, days since 2000-01-01), but DoltgreSQL sends a 10-byte text string like 2026-01-15.
Reproduction
// Table with TIMESTAMPTZ and DATE columns:
// CREATE TABLE events (
// id UUID PRIMARY KEY,
// created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
// event_date DATE
// );
#[derive(sqlx::FromRow)]
struct Event {
id: Uuid,
created_at: DateTime<Utc>, // expects 8-byte binary
event_date: Option<NaiveDate>, // expects 4-byte binary
}
// This fails — sqlx tries to decode text bytes as binary:
let event = sqlx::query_as::<_, Event>("SELECT * FROM events WHERE id = $1")
.bind(some_id)
.fetch_one(&pool)
.await?;SQLx's DateTime<Utc> decoder expects 8 bytes (i64 microseconds) but receives 29 bytes of text. Similarly, NaiveDate expects 4 bytes (i32 days) but receives 10 bytes of text.
Workaround
We use newtype wrappers that detect the format by buffer size and decode accordingly:
impl<'r> sqlx::Decode<'r, sqlx::Postgres> for DoltTimestamp {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let bytes = value.as_bytes()?;
if bytes.len() > 8 {
// DoltgreSQL text format
let s = std::str::from_utf8(bytes)?;
let dt = DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S%.f%#z")?;
Ok(DoltTimestamp(dt.with_timezone(&Utc)))
} else if bytes.len() == 8 {
// Standard PostgreSQL binary
let micros = i64::from_be_bytes(bytes.try_into()?);
let pg_epoch = NaiveDate::from_ymd_opt(2000, 1, 1).unwrap()
.and_hms_opt(0, 0, 0).unwrap().and_utc();
Ok(DoltTimestamp(pg_epoch + Duration::microseconds(micros)))
} else {
Err("unexpected buffer size".into())
}
}
}This works but requires wrapping every DateTime<Utc> and NaiveDate field in our entire codebase (30+ structs).
Expected behavior
The extended query protocol should send TIMESTAMPTZ as 8-byte big-endian i64 (microseconds since 2000-01-01 00:00:00 UTC) and DATE as 4-byte big-endian i32 (days since 2000-01-01), matching standard PostgreSQL binary format.
Environment
- DoltgreSQL:
dolthub/doltgresql:latest(Docker), tested on 0.54.4 through 0.55.0 - Client: SQLx 0.8 (Rust) via extended query protocol