Skip to content

feat(api): carry type size and modifier in RowDescription#449

Merged
sunng87 merged 2 commits into
sunng87:masterfrom
SebastianThiebaud:typmod-passthrough
Jul 26, 2026
Merged

feat(api): carry type size and modifier in RowDescription#449
sunng87 merged 2 commits into
sunng87:masterfrom
SebastianThiebaud:typmod-passthrough

Conversation

@SebastianThiebaud

@SebastianThiebaud SebastianThiebaud commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Problem

From<&FieldInfo> for FieldDescription hardcodes the type size and modifier, with a TODO:

fi.datatype.oid(),         // type_id
// TODO: type size and modifier
0,
0,

For the type modifier this is not just imprecise, it is actively wrong. PostgreSQL sends -1 when a type has no modifier; 0 is not "unset" but a valid-looking modifier. Clients decode it and derive a display scale from it: pgJDBC computes a numeric's scale as (typmod - 4) & 0xffff, so 0 yields a nonsense scale.

The user-visible symptom: an unconstrained numeric holding 1 renders in DataGrip (and other JDBC-based clients) as

1.00000000000000000000000000000000000000000000000000000000000000000000000...

A declared numeric(18, 4) is affected too: it cannot advertise its real precision and scale, so clients cannot format it correctly or report it through ResultSetMetaData.

Since FieldInfo has no field for either value, a server built on pgwire currently has no way to send a correct RowDescription, even when it knows the right answer.

Change

Adds type_size and type_modifier to FieldInfo and passes them through to FieldDescription.

This is source-compatible. Both are #[new(value = ...)] defaulted fields, so FieldInfo::new keeps its current five-argument signature and no existing caller breaks. They are set with with_type_size / with_type_modifier, mirroring the existing with_format_options builder.

The one behavior change is deliberate and is the fix itself: type_modifier now defaults to -1 instead of 0, so servers that do not set it emit PostgreSQL's "no modifier" encoding rather than a modifier that decodes to garbage. type_size keeps its 0 default.

The reverse FieldDescription -> FieldInfo conversion carries both values back, so a round trip no longer discards them.

Tests

Two unit tests in src/api/results.rs:

  • the default modifier is -1, and reaches FieldDescription as -1
  • an explicit numeric(18, 4) modifier and type size reach FieldDescription and survive the round trip back into a FieldInfo

I verified the change end to end against a real client: with it applied, a server that previously advertised typmod=0 on every column now advertises -1 for unconstrained types and the correctly packed ((p << 16) | s) + 4 for declared numeric(p, s), and DataGrip renders the values correctly.

I was not able to run the full suite locally: the rusqlite dev-dependency fails to compile on my toolchain (error[E0658]: use of unstable library feature 'cfg_select'), which is unrelated to this change. cargo build is clean.

`FieldInfo` hardcoded `type_size` and `type_modifier` to `0` when converting
into `FieldDescription`, with a TODO to fill them in.

For the modifier this is not merely imprecise. `0` is not "unset": PostgreSQL
sends `-1` when a type has no modifier, and clients decode a `0` as a real
one. For `numeric` that yields a nonsense display scale, so JDBC-based clients
render every value with a long tail of spurious zeros — an unconstrained
`numeric` holding `1` displays as `1.000...`.

Add `type_size` and `type_modifier` to `FieldInfo` as defaulted fields, so
`FieldInfo::new` keeps its current signature, with `with_type_size` /
`with_type_modifier` builders mirroring the existing `with_format_options`.
The modifier defaults to `-1`. Both values now reach `FieldDescription`, and
the reverse `FieldDescription -> FieldInfo` conversion carries them back.
@sunng87

sunng87 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

@SebastianThiebaud Thank you! Could you fix the rustfmt issue? I will get this merged soon.

@SebastianThiebaud

Copy link
Copy Markdown
Contributor Author

@SebastianThiebaud Thank you! Could you fix the rustfmt issue? I will get this merged soon.

Done!

@sunng87
sunng87 merged commit 5c8d872 into sunng87:master Jul 26, 2026
10 checks passed
@SebastianThiebaud
SebastianThiebaud deleted the typmod-passthrough branch July 26, 2026 17:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants