8000 feat(postgres)!: return an hstore column as an object by colinhacks · Pull Request #98 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

feat(postgres)!: return an hstore column as an object - #98

Merged
colinhacks merged 2 commits into
mainfrom
feat-hstore-objects
Oct 23, 2026
Merged

colinhacks merged 2 commits into
mainfrom
feat-hstore-objects

Conversation

@colinhacks
Copy link
Copy Markdown
Owner

An hstore column comes back from .run() as Record<string, string | null> instead of its text, and its parameter takes one. Inside a JSON document Postgres already writes an hstore as that object (through hstore's cast to JSON), which Reylo typed, so a column and a document field of the same value now agree. Both Neon and Supabase ship hstore.

  • A new kind, hstore (src/values.ts), and a parser of hstore's text: every key and value quoted, " and \ escaped, a bare NULL for a null value, , between pairs ("a"=>"1", "b c"=>NULL is { a: "1", "b c": null }). The object is built with Object.fromEntries, so a key named __proto__ stays a key. hstore[] is an array of them.
  • Parameters (src/execute.ts, src/rows.ts): an object is written back as the text hstore reads; a value that is not a string or null, or an array, is refused before the query runs, and a key whose value is undefined is left out. The text itself still passes. An array of hstores goes as an array literal of those texts.
  • Bun.sql, which hands an hstore over as text, converts it; a query defined by hand learns the type by its name (the catalog lookup from feat(postgres)!: return pgvector's vector and halfvec as number[] #90).
  • The Postgres docs page's type and parameter tables say so, and the paragraph on values that differ inside a document no longer lists hstore; the skill names it beside vector.

Checked live, through modules the loader built against Supabase's and Neon's Postgres 17 images: pg, postgres.js, PGlite with its hstore and Bun.sql return the same objects (keys with quotes, backslashes, spaces, =>, an empty key and __proto__; hstore[] with an empty hstore), and bind them back (an insert, prefs @> :p, a rows(…) member, a hand-defined query); each column equals what json_build_object writes for it. Unit tests cover the parser and writer against the server's own text, the decoders on every driver's path, the binding and its refusals, the types and schemas.

An hstore column came back as its text; inside a JSON document Postgres
already wrote it as an object of its keys, which Reylo typed. It is that
object everywhere now, Record<string, string | null>, and a parameter
or a rows() member takes one, written back as the text hstore reads.
Copilot AI balanced review requested due to automatic review settings October 23, 2026 23:10
@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 11:22pm 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 rough edge inline.

Reviewed changes

I reviewed the whole PR: the new hstore kind, its parser and writer, how it binds as a parameter and as a rows(…) member, Bun.sql's decode path, the wire schema, and the docs and skill.

  • hstore kind: SQL_KINDS, TS_TYPE, TEXT_DECODERS and coerce in src/values.ts now return an hstore as Record<string, string | null>. hstore[] goes through the existing parseArray.
  • Parser and writer: hstore() reads the server's canonical text and hstoreText() writes the input text. I traced both against hstore's output and input rules (quoting, \ escapes, bare NULL) and found no mismatch.
  • Parameters: encode in src/execute.ts and scalar() in src/rows.ts turn an object into hstore text. An hstore[] parameter goes through the existing array branch as an array of those texts.
  • Bun.sql: BUN_KINDS now includes hstore, so Bun's text, and its text array for hstore[], are converted after the fact.
  • Schema, docs, skill: the wire schema in kindSchema now matches the in-document schema. The Postgres page's type and parameter tables and the skill describe the new form.

The targeted tests (--test-name-pattern=hstore across values, rows, render and execute) pass locally. engine/core/src/schema.rs still writes the wire hstore as a plain string. Only tests call that function, though, and #90 left vector the same way, so I've left it alone.

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

Comment thread src/values.ts 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 rough edge is fixed.

Reviewed changes

I reviewed the one commit since the last review, 26040ed, which tightens what hstoreText accepts.

  • Replaced the deny-list with a plain-object check: hstoreText in src/values.ts now refuses any value whose prototype isn't Object.prototype or null. A Map, a Set or a class instance is now refused instead of being written as an empty hstore.
  • Error message names the constructor: the error now says "got a Map." or "got a Prefs.". Arrays, a Date and typed arrays still get their describe wording.
  • Added tests in tests/values.test.ts for a Map, a class instance and a null-prototype object, which is still accepted.

I checked both callers, encode in src/execute.ts and scalar() in src/rows.ts: each passes only non-null objects, so the new Object.getPrototypeOf call never sees null. The hstore tests in values, rows and execute pass locally.

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

@colinhacks
colinhacks merged commit ec01766 into main Oct 23, 2026
6 checks passed
@colinhacks 8000
colinhacks deleted the feat-hstore-objects branch October 23, 2026 23:46
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