Repository navigation
feat(postgres)!: return an interval as an iso 8601 duration - #93
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
ℹ️ No critical issues — two rough edges inline, both about where Postgres writes an interval in a form other than its default.
Reviewed changes
Reviewed the single commit that switches a Postgres interval's run-time representation from Postgres's text to an ISO 8601 duration, and the parameter rewrite that makes it round-trip.
intervalkind:src/values.tsadds the kind,duration(Postgres's text to ISO 8601, with a leading sign when every part is negative and per-part signs when they differ) andpostgresInterval(-P…to the per-part form Postgres reads).- Decoding:
src/decode.tsmaps oid 1186 tointervaland sends Bun.sql's interval text throughcoerce. - Parameters:
src/execute.ts(plain and array) andsrc/rows.ts(structured member) rewrite a leading-sign duration before binding. - Output schema:
src/schema.tsdescribes the form without claiming JSON Schema'sdurationformat. - Docs, tests, driver matrix: the Postgres page, the value/decode/execute/representation tests, and the matrix's expected
typedrow follow the new form.
I traced duration against Postgres's postgres-style output (the + after a negative part, hours past 24, fractions with no whole seconds, both extremes) and postgresInterval against Temporal's output (fractions, W). Both handle these correctly.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
✅ No new issues found. Both earlier findings are fixed.
Reviewed changes
Reviewed the two commits pushed since the last review. Both fix what that review raised.
- Interval inside JSON: in
src/schema.ts, theinJsonswitch inscalar()now describes an interval inside a document aspg type interval, which is Postgres's own text, so it no longer gets the ISO 8601 description. A test intests/values.test.tspins this. - Docs caveats: the Postgres page now says the ISO 8601 form holds only in the default
IntervalStyle. Its "Values inside a document" list also gains the interval, with ajsonb_build_objectpair.
claude-opus-5-5 | 𝕏
4e903a7 to
0d39b4e
Compare
An interval was Postgres's own text, which nothing in JavaScript parses. It is now an ISO 8601 duration with its sign in front, which Temporal.Duration.from reads; an interval whose parts differ in sign keeps Postgres's per-part signs. A parameter takes either form.
0d39b4e to
12ce210
Compare

A Postgres
intervalcame back as Postgres's own text (1 day 02:03:04), which nothing in JavaScript parses. It now comes back as an ISO 8601 duration with its sign in front, whichTemporal.Duration.fromreads:1 day 02:03:04"1 day 02:03:04""P1DT2H3M4S"1 year 2 mons"1 year 2 mons""P1Y2M"0"00:00:00""PT0S"-1 day"-1 days""-P1D"1 mon -1 day"1 mon -1 days""P1M-1D"infinity"infinity""infinity"Breaking: code that reads an interval's text gets the new form. The TypeScript type stays
string.The value is what Postgres itself writes in
IntervalStyle = iso_8601, with one change. Postgres signs each part (P-1D), which ISO 8601 and Temporal refuse, so an interval whose parts are all negative takes a single sign in front (-P1D). An interval whose months, days and time differ in sign has no ISO 8601 form, so it keeps Postgres's per-part signs (P1M-1D). Postgres reads that form back; Temporal refuses it.Values still round-trip: Postgres refuses a sign before the
P, so an interval parameter's-P…is rewritten to the per-part form before binding, in a plain parameter, an array element and a structured parameter's member. Postgres's own text is still accepted as a parameter.src/values.ts: theintervalkind,duration(Postgres's text to ISO 8601) andpostgresInterval(a parameter's-P…to the form Postgres reads).src/decode.ts: oid 1186 is aninterval, and Bun.sql's interval text is converted too.src/execute.ts,src/rows.ts: parameters.src/schema.ts: the output schema describes the form. It doesn't claim JSON Schema'sdurationformat, because RFC 3339's duration has no sign and no fraction.typedrow.Checked against Postgres 18 on 22 intervals, including negatives, mixed signs, fractions, hours past 24 and both extremes. Each value equals Postgres's own ISO 8601 output with the sign moved to the front, binds back to the identical interval part for part, and, where its parts share a sign, is read by Node 26's
Temporal.Duration.from. The loader tests (none skipped) and every driver matrix job (4,524 rows) pass.