8000 feat(postgres): read each column's type modifier by colinhacks · Pull Request #180 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

feat(postgres): read each column's type modifier - #180

Merged
colinhacks merged 4 commits into
mainfrom
pg-typmods
Nov 4, 2026
Merged

colinhacks merged 4 commits into
mainfrom
pg-typmods

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

The Postgres engine reads each column's type modifier, so a column's type descriptor carries the length, precision or tick it was declared with: varchar(255) is text of at most 255 code points, numeric(10,2) a decimal of precision 10 and scale 2, timestamp(3) an instant ticking by a millisecond.

Declared Descriptor
varchar(20) { "kind": "text", "size": { "max": 20 }, "name": "varchar" }
char(5) { "kind": "text", "size": { "exact": 5 }, "name": "bpchar" }, since it pads what it stores
numeric(10,2) { "kind": "decimal", "precision": 10, "min_scale": 2, "max_scale": 2, "nan": true, "name": "numeric" }: NaN, and no infinity, which a sized numeric refuses
numeric(5,-2) precision 5, scale -2
time(3), timestamp(3), timestamptz(0) tick: "1/1000", or "1", and the latest value cut to the tick
interval year to month, interval day, interval hour, interval hour to minute, interval second(3) months alone; months and days; a time part ticking by 3600, 60 or 1/1000 seconds
bit(7), varbit(5) an array of exactly 7, or at most 5, booleans
vector(3), halfvec(3) an array of exactly 3 floats
varchar(20)[] the modifier on each element
PostGIS geometry(Point, 4326), geometry(LineStringZ, 3857) srid, subtype and coordinates: 4326, point, xy

Where the modifier comes from

  • A result column: RowDescription's type modifier, which each host now sends with its describe (typmod, -1 for none): the native program, the facade's tokio-postgres client and the docs site's PGlite host. It covers a table's column, a view's, a cast (name::varchar(3)) and an expression that keeps one (coalesce(name, name), tags[1]). A column of a domain is described as its base type under the domain's modifier.
  • A domain or a composite type's field: pg_type.typtypmod and pg_attribute.atttypmod, read with the type itself, so create domain email as varchar(255) and a composite's zip varchar(10) carry their sizes inside rows and JSON too.

A host that sends no typmod gives the engine none, which leaves every type as it was. Parameters are unchanged: the describe reports no modifier for one.

What changes for a user

The declarations and the values .run() returns are the same: the docs capture is unchanged, all 182 modules. reylo analyze prints the new fields, and a sized numeric column's JSON Schema leaves out Infinity and -Infinity, which it cannot hold.

Checks

  • A new oracle case, type_modifiers, reads each modifier through a column, a cast, a domain, a composite, an array, a view and an interval's fields, over rows at each type's edges. The oracle now holds a value to its size, its precision and scale, its tick and its interval parts, besides what it held before; Postgres's and SQLite's 127 cases pass under the stricter checks.
  • Three expected.txt files change, each a column gaining its modifier: check_unions (varchar(≤10, 'free' | 'pro'), bpchar(=3)), check_non_finite (numeric(10,2), a composite's field included) and pgvector_types (vector(3), halfvec(3)).
  • A unit test of each encoding, as Postgres 18 and PostGIS 3.6 store it.
  • cargo build --workspace --all-targets with no warning, every crate's tests, nub run typecheck, and nub run test with both test databases (340 passed, none skipped).

Copilot AI balanced review requested due to automatic review settings November 4, 2026 19:17
@vercel
vercel Bot commented Nov 4, 2026 •
Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Gi 8000 tHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Nov 4, 2026 7:58pm 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. Try requesting a review at a lower effort level.

@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.

Important

The schema fingerprint that keys .reylo/ doesn't track type modifiers, so a migration that changes only a modifier keeps serving the old descriptor. There is also one oracle false positive, noted inline.

Reviewed changes

I reviewed the full diff of e41b588: the hosts now send RowDescription's typmod, the Postgres engine decodes it into the type descriptor, and the oracle's value checks are stricter.

  • Hosts send typmod: the native program, the facade's tokio-postgres client and the PGlite host each forward each column's type modifier. A missing one defaults to -1.
  • Type registry reads modifiers: the type query now also reads typtypmod and each composite field's atttypmod. render_at passes a modifier through arrays, domains and composite fields, and with_typmod decodes the Postgres and PostGIS encodings. I checked these against varchar, numeric (the 11-bit signed scale), interval masks and TYPMOD_GET_*, and they match.
  • Result columns use it: the plain-column path in postgres.rs now calls render_typmod. Parameters of a domain or composite type pick up the catalog's modifiers through render. That matches the spec's rule that a parameter may be stricter than the server, never looser.
  • Oracle is stricter: seed::contradiction, which the Postgres and SQLite runs use, now checks text lengths, decimal precision and scale, ticks, interval parts, and bit and vector lengths. There is a new type_modifiers case, and three blessed expected.txt files changed.

⚠️ The schema fingerprint ignores type modifiers, so cached analyses go stale

A Postgres analysis now depends on atttypmod and typtypmod. But POSTGRES_FINGERPRINT in src/analyze.ts hashes a.atttypid and t.typbasetype and leaves out both modifiers. So alter table t alter column name type varchar(50) on an unindexed column leaves the fingerprint unchanged, and the .reylo/ entry keeps varchar(≤20). Changing numeric to numeric(10,2) has the same problem: the cached output JSON Schema keeps or drops Infinity according to the old type.

Technical details
# Fingerprint the type modifiers the analysis now reads

## Affected sites
- src/analyze.ts:560 — the `a` line of `POSTGRES_FINGERPRINT` hashes `atttypid` but not `atttypmod`
- src/analyze.ts:568 — the `t` line hashes `typbasetype` but not `typtypmod` (`create domain d as varchar(n)`)
- src/analyze.ts:525-536 — the doc comment listing what the fingerprint covers

## Required outcome
- A change to a column's or a domain's type modifier, with nothing else changed, changes the Postgres fingerprint, so the `.reylo/` entries keyed on it are analyzed again.

## Suggested approach
- Add `out("int4", "a.atttypmod")` to the `a` line and `out("int4", "t.typtypmod")` to the `t` line, keeping the `textin(int4out(…))` form the query uses for search-path safety.
- Add a test beside the existing fingerprint tests: alter a column's type modifier and assert that the fingerprint moves.

ℹ️ Fields built into JSON don't get the modifier

ResolveCtx::column in engine/postgres/src/jsonshape.rs:1481 still calls registry.render(col.type_oid). So json_build_object('total', total) types total as plain numeric (check_non_finite/built.sql), while to_jsonb(row) gives numeric(10,2) through the composite's fields (whole_rows.sql). This is sound, just less precise. If the omission isn't intended, passing col.typmod through render_typmod there would make the two paths agree.

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

Comment thread oracle/src/seed.rs Resolved
@colinhacks
Copy link
Copy Markdown
Owner Author

Both points from the review are addressed:

  • The fingerprint now covers type modifiers (ab5fff8). The a line hashes a.atttypmod and the t line hashes t.typtypmod, both through textin(int4out(…)). The doc comment lists them, and the fingerprint test now checks that alter column v type varchar(50) and alter column n type numeric(10,2) each move it.
  • Fields built into JSON now keep the modifier (ddf88fd). ResolveCtx::column renders a probe column with render_typmod(col.type_oid, col.typmod), so json_build_object agrees with to_jsonb(row). Two cases were re-blessed: check_non_finite/built.sql (total: numeric(10,2)) and pgvector_types (json_agg, json_object and whole_row gain [3]). The Postgres oracle cases all pass, and so do the engine tests, the workspace build with no warnings, nub run typecheck and nub run test (340 passed, none skipped).

@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 three commits address each point from the prior review.

Reviewed changes

I reviewed the three commits since e41b588: a fix to the fingerprint, a fix to the oracle's decimal check, and type modifiers in fields built into JSON.

  • Fingerprinted type modifiers: POSTGRES_FINGERPRINT now hashes a.atttypmod and t.typtypmod. The fingerprint test asserts that changing varchar(20) to varchar(50) moves it, and so does changing numeric to numeric(10,2).
  • Fixed the decimal precision count: decimal_contradiction now counts the significant digits of the whole and fractional parts together, with leading zeros trimmed. So 0.000123 fits a numeric(3,6), while 0.001230 breaks it. A new unit test covers that case, negative scales and ordinary values. Zero under a negative scale still passes, because the trimmed whole is empty.
  • Kept modifiers in JSON-built fields: ResolveCtx::column 8000 now calls render_typmod. As a result, json_build_object('total', total) gives numeric(10,2), which matches to_jsonb(row). The check_non_finite and pgvector_types expected files change to match.

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

@colinhacks
colinhacks merged commit fe944c8 into main Nov 4, 2026
33 checks passed
@colinhacks
colinhacks deleted the pg-typmods branch November 4, 2026 21:07
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

0