Skip to content

Integer DDL types collapse wire OID fidelity — INT/INTEGER/INT4/BIGINT all report OID 20; SMALLINT/INT2 report OID 25 (text) #217

Description

@emanzx

Version / build tested against

farhansyah/nodedb:0.4.0 (published Docker image; SELECT version() → NodeDB 0.4.0, wire format 1). Not re-tested against current origin/main — close if already addressed.

Deployment mode

Origin — single node (local), observed at the wire level through a pgwire client (Crystal / nodedb.cr, decoding RowDescription OIDs directly).

Engine(s) involved

pgwire / client type fidelity — engine-independent DDL type mapping.

Summary

Every integer DDL keyword except SMALLINT/INT2 reports wire type OID 20 (int8/bigint) in RowDescription, regardless of the declared width — INT, INTEGER, INT4, BIGINT, and INT8 all come back as OID 20. SMALLINT/INT2 are worse: they report OID 25 (text), not int2, and the payload arrives as plain decimal text. A pgwire client that decodes strictly by the reported OID therefore (a) reads every INT/INTEGER column as 8-byte where it declared 4-byte, and (b) gets a text value where it expects a 2-byte integer for SMALLINT. The declared type and the wire-reported type disagree.

Steps to reproduce

DROP COLLECTION IF EXISTS fixprobe_ints;
CREATE COLLECTION fixprobe_ints (id TEXT PRIMARY KEY, a INT, b INTEGER, c INT4, d BIGINT, e INT8, f SMALLINT, g INT2);
INSERT INTO fixprobe_ints (id, a, b, c, d, e, f, g) VALUES ('r1', 1, 2, 3, 4, 5, 6, 7);
SELECT id, a, b, c, d, e, f, g FROM fixprobe_ints;

Inspect the RowDescription type OIDs on the wire (psql \gdesc, or any client that exposes column OIDs):

Column DDL type Reported OID Reported as
a INT 20 int8/bigint
b INTEGER 20 int8/bigint
c INT4 20 int8/bigint
d BIGINT 20 int8/bigint
e INT8 20 int8/bigint
f SMALLINT 25 text (not int2!)
g INT2 25 text (not int2!)

Re-verified on a second throwaway collection (f SMALLINT, g INT2 only, values 100, 200) — same result: oid=25 for both, row values arrive as plain decimal text ("100", "200"), no quoting/JSON wrapping.

Expected behavior

RowDescription OIDs should reflect the declared width: INT/INTEGER/INT4 → OID 23 (int4), BIGINT/INT8 → OID 20 (int8), SMALLINT/INT2 → OID 21 (int2). A Postgres-wire-compatible server's whole value proposition is that a stock pg client can decode by OID; today a client must special-case NodeDB (treat all INT as int8, and parse SMALLINT from text).

Actual behavior

All 4-byte and 8-byte integer keywords collapse to OID 20; SMALLINT/INT2 fall through to OID 25 text. Values are still correct once decoded with this knowledge, so it is a type-fidelity/compat defect, not data loss.

What actually happened? (check all that are true)

  • Acknowledged/committed data was lost, corrupted, or silently wrong
  • The server crashed, hung, or failed to start
  • A security or isolation boundary was crossed
  • Core functionality is broken with no acceptable workaround
  • A workaround exists (rewrite the query, avoid one path, etc.)

Proposed severity

SEV-3 — Medium: breaks strict-by-OID pgwire clients (they read wrong widths or get text for SMALLINT), but values decode correctly once the client special-cases it. Workaround: decode all integer columns as int8, and SMALLINT/INT2 as text.

Reproducibility

Always — every attempt.

Environment & logs

Linux x86_64, farhansyah/nodedb:0.4.0 Docker image, single local node. Observed by decoding RowDescription OIDs directly through nodedb.cr's wire client. The nodedb.cr shard now documents "read NodeDB INT as Int64, SMALLINT/INT2 as text" as a known dialect quirk in its wire-facts.

Activity

  1. added
    type:bugA defect — broken, incorrect, or lost data
    status:needs-triageAwaiting maintainer triage (severity + priority)
    area:pgwirePostgreSQL wire protocol / client compat
    sev:3-mediumFeature wrong, but operational and a workaround exists
    and removed
    status:needs-triageAwaiting maintainer triage (severity + priority)
    on Jul 25, 2026
  2. emanzx commented on Jul 26, 2026

    @emanzx
    ContributorAuthor

    Re-verified on origin/main @ f7fcc7718 (release build): still reproduces, with a neat self-documenting artifact — psql's \gdesc embeds the server-reported OIDs in a VALUES describe query, and nodedb's rejection error echoes them verbatim:

    VALUES ('id', '25'::pg_catalog.oid, 0), ('a', '20'::pg_catalog.oid, 0), ('b', '20'::pg_catalog.oid, 0),
           ('c', '20'::pg_catalog.oid, 0), ('d', '20'::pg_catalog.oid, 0), ('e', '20'::pg_catalog.oid, 0),
           ('f', '25'::pg_catalog.oid, 0), ('g', '25'::pg_catalog.oid, 0)
    

    i.e. INT/INTEGER/INT4/BIGINT/INT8 → OID 20, SMALLINT/INT2 → OID 25 (text), exactly as filed. (Bonus observation from the same probe: VALUES-body queries themselves are unsupported — unsupported: query body type: VALUES ... — which is why \gdesc errors instead of rendering.) The staleness caveat in the report body no longer applies.

  3. added a commit that references this issue on Jul 26, 2026
    e0cb183
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:pgwirePostgreSQL wire protocol / client compatpriority:P2Scheduled, not urgentsev:3-mediumFeature wrong, but operational and a workaround existstype:bugA defect — broken, incorrect, or lost data

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions