Skip to content

fix(postgres): type oid and extension types inside json as written - #85

Merged
colinhacks merged 2 commits into
mainfrom
fix-json-ext-types
Oct 23, 2026
Merged

colinhacks merged 2 commits into
mainfrom
fix-json-ext-types

Conversation

@colinhacks

Copy link
Copy Markdown
Owner

Inside a JSON document (json_build_object, to_jsonb(t), json_agg), Postgres writes a value through its type's cast to JSON where one exists, a number only for the integers, the floats and numeric, and anything else as its text. Three spellings disagreed with that:

  • oid, and the lo domain over it, are JSON strings. json_build_object('o', 7::oid) is {"o" : "7"}, which Reylo typed number (schema integer). The oracle failed a case over lo/oid columns on 12 values. This holds with no extension involved.
  • A PostGIS geometry is GeoJSON. PostGIS's cast to JSON writes {"type":"Point","coordinates":[1,2]}; the TypeScript type was unknown (sound) but the JSON Schema said string. It is now { type: string; [key: string]: unknown } with an object schema in both schema writers.
  • Every other extension type on Neon and Supabase has no cast to JSON, so it is its text: pgvector's vector, halfvec, sparsevec; PostGIS's geography, box2d, box3d, spheroid, raster; cube, seg, the isn types, query_int, h3index, hll, the ip4r types, ulid, prefix_range, rdkit's types, roaringbitmap, semver and unit with their ranges. These rendered unknown; they are string now, as are the built-in geometric and system types (point, box, pg_lsn, the reg* types).

The oracle's mirror of the table (json_admits) follows, and a new case, json_extension_types, checks lo, oid, cube, earth, seg, isbn and query_int inside documents on CI's contrib extensions. Every type was measured on Supabase's and Neon's Postgres 17 images. The Postgres docs page's paragraph on values inside a document names the oid, geometry and extension cases.

Copilot AI balanced review requested due to automatic review settings October 23, 2026 20:52
@vercel

vercel Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Oct 23, 2026 9:54pm UTC

Request Review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues. One docs wording suggestion inline.

Reviewed changes

I reviewed the full diff: how Postgres writes oid, geometry and extension types inside a JSON document, in both schema writers, the renderer, the oracle's mirror and the docs.

  • oid inside a document is text: the json table in src/render.ts, the inJson branch in src/schema.ts and json_admits in oracle/src/seed.rs now type oid (and the lo domain, which resolves to it) as string. This matches json_categorize_type, which writes JSON numbers only for the integer, float and numeric OIDs.
  • geometry inside a document is GeoJSON: the type is now { type: string; [key: string]: unknown }, with an object schema in src/schema.ts and in engine/core/src/schema.rs. All three engine.wasm artifacts are rebuilt for the core change.
  • Types with no cast to JSON are text: the built-in geometric and system types, and the listed extension types, are now string instead of unknown. The lists in src/render.ts and json_admits match entry for entry.
  • Oracle case: json_extension_types covers lo, oid, cube, earth, seg, isbn and query_int on CI's contrib extensions.
  • Docs: the Postgres page's "Values inside a document" paragraph and its examples.

I also checked two things that turned out fine:

  • mysqlJson inherits the new geometry and point entries. That has no effect, because the MySQL engine maps GEOMETRY (code 255) to Type::Unknown, so neither name ever reaches the table.
  • In Rust, oid inside a document still carries description: "pg type oid", while src/schema.ts gives a plain { type: "string" }. That difference doesn't matter: Type::json_schema is only called from its own tests.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread site/content/docs/postgres.mdx Resolved

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The earlier docs suggestion has been applied.

Reviewed changes

The only change since the last review is one docs sentence.

  • Narrowed the extension-type rule in site/content/docs/postgres.mdx: the sentence now says only an extension's type with no cast to JSON (e.g. pgvector's vector) is written as its text. This matches the code. geometry goes through its own cast and comes out as GeoJSON, and an extension type that isn't in the table still renders unknown.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

Inside a JSON document Postgres writes a number only for the integers,
the floats and numeric, so an oid (and the lo domain) is its text, not
a number. PostGIS writes a geometry through its cast to JSON, as
GeoJSON, where the schema said a string. pgvector's, PostGIS's other
types and the other extensions' with no cast to JSON are their text,
which rendered unknown; they and the built-in geometric and system
types are typed string there now.
@colinhacks
colinhacks merged commit 7caf9b2 into main Oct 23, 2026
6 checks passed
@colinhacks
colinhacks deleted the fix-json-ext-types branch October 23, 2026 22:00
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