Repository navigation
feat(postgres)!: return an hstore column as an object - #98
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
ℹ️ 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.
hstorekind:SQL_KINDS,TS_TYPE,TEXT_DECODERSandcoerceinsrc/values.tsnow return an hstore asRecord<string, string | null>.hstore[]goes through the existingparseArray.- Parser and writer:
hstore()reads the server's canonical text andhstoreText()writes the input text. I traced both against hstore's output and input rules (quoting,\escapes, bareNULL) and found no mismatch. - Parameters:
encodeinsrc/execute.tsandscalar()insrc/rows.tsturn an object into hstore text. Anhstore[]parameter goes through the existing array branch as an array of those texts. - Bun.sql:
BUN_KINDSnow includeshstore, so Bun's text, and its text array forhstore[], are converted after the fact. - Schema, docs, skill: the wire schema in
kindSchemanow 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.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
✅ 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:
hstoreTextinsrc/values.tsnow refuses any value whose prototype isn'tObject.prototypeornull. AMap, aSetor 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
Dateand typed arrays still get theirdescribewording. - Added tests in
tests/values.test.tsfor aMap, 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.
claude-opus-5-5 | 𝕏

An
hstorecolumn comes back from.run()asRecord<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.hstore(src/values.ts), and a parser of hstore's text: every key and value quoted,"and\escaped, a bareNULLfor a null value,,between pairs ("a"=>"1", "b c"=>NULLis{ a: "1", "b c": null }). The object is built withObject.fromEntries, so a key named__proto__stays a key.hstore[]is an array of them.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 isundefinedis left out. The text itself still passes. An array of hstores goes as an array literal of those texts.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, arows(…)member, a hand-defined query); each column equals whatjson_build_objectwrites 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.