runtimes/js (sqldb): expose column metadata (OID, type name) on rawQueryAll results#2417
Open
shimonenator wants to merge 1 commit intoencoredev:mainfrom
Open
runtimes/js (sqldb): expose column metadata (OID, type name) on rawQueryAll results#2417shimonenator wants to merge 1 commit intoencoredev:mainfrom
rawQueryAll results#2417shimonenator wants to merge 1 commit intoencoredev:mainfrom
Conversation
…eryAll results Add a `columns` property to the augmented array returned by `rawQueryAll` and related query methods. Each entry contains the column name, PostgreSQL type OID, and type name for the corresponding field in the result set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Context
I am building a Prisma driver adapter that lets Encore.ts
SQLDatabaseinstances be used as the underlying connection for Prisma ORM. The main objective is to enable tracing and make debugging easier, as queries sent via regularprisma-pgadapter go invisible in the dashboard.Prisma's driver adapter interface requires each query result to include a
columnTypesarray alongsidecolumnNamesandrows. ThecolumnTypesvalues come from Prisma'sColumnTypeEnumand tell Prisma how to interpret each value before handing it to the application — for example, whether to callJSON.parse, whether to convert toBigInt, or whether to pass the value through as-is.Without type metadata from the driver, the adapter has no reliable way to populate
columnTypescorrectly. I have tried to automate data type guessing, as it's generally not hard, but there are instances where a single shape can be representing two different column types, and Prisma expects them to be returned in a different format.This is what it looks like for queries that work:


The Problem: JSONB Without Type Metadata
PostgreSQL's
JSONBtype is where this gap is most visible. Encore automatically deserializes JSONB column values into native JavaScript types — objects become plain JS objects, arrays become JS arrays, strings become JS strings. This is convenient in general, but creates an ambiguity for the adapter:jsonbcolumn returning[1, 2, 3]looks identical to anint4[]column returning[1, 2, 3]jsonbcolumn returning"hello"looks identical to atextcolumn returning"hello"jsonbcolumn returning{ "key": 1 }looks identical to any other column returning a plain JS objectWithout knowing the column's actual PostgreSQL type, the adapter must guess — and guessing wrong produces silent data corruption in the application layer.
What Breaks Without Type Information
JSONB arrays returned as native arrays
Prisma processes
Jsonschema fields using template literal coercion:`${value}`. For a plain JS object this produces a valid JSON string. For a JS array it does not:Prisma then calls
JSON.parse("1,2,3")which throws. The result: afindManyon a model with aJsonfield that holds an array value crashes, or returns corrupted data.JSONB strings returned as bare JS strings
A JSONB string value
"hello"(stored as"hello"in PostgreSQL) is returned by Encore as the plain JS stringhello. Prisma's template literal gives:The application receives an error or an empty value instead of the string
"hello".Typed arrays misidentified as JSONB
The reverse is also a problem: an
int4[]column returning[1, 2, 3]might be mistakenly treated as JSONB by a heuristic, causing the adapter toJSON.stringifythe array before returning it. Prisma then receives"[1,2,3]"instead of[1, 2, 3], and integer array fields return the wrong type to the application.What Column Metadata Enables
With
typeOid,tableOid(null for generated columns), andtypeNameavailable on the result, the adapter can:Return correct
ColumnTypeEnumvalues —jsonb(OID 3802) maps toColumnTypeEnum.Json;_jsonb(OID 3807) maps toColumnTypeEnum.JsonArray;int4[](OID 1007) maps toColumnTypeEnum.Int32Array. Prisma uses this to apply the correct deserialization path.Normalize values correctly per column — For
jsonbcolumns, the adapter canJSON.stringifyarrays and bare strings before returning them, so Prisma's template literal coercion produces valid JSON thatJSON.parsecan consume.Resolve column identity in JOIN results —
tableOididentifies which table a column originates from, making it possible to disambiguate two columns with the same name but different types coming from different tables in a single query. Generated columns carry a nulltableOidsince they are not associated with a specific table.Distinguish ambiguous types at runtime — No heuristics, no schema file parsing, no startup query against
information_schema.columns. The type identity comes directly from the query response, just as it does in the standardpgNode.js driver viafield.dataTypeIDandfield.tableID.This brings the Encore adapter to parity with
@prisma/adapter-pg, which has relied on field OIDs for correct JSON and array handling since its initial release.Why This Is a Compatibility Gap, Not a Bug
Encore's automatic JSONB deserialization is the right default for application code that uses
rawQueryAlldirectly — receiving a plain JS object is more ergonomic than receiving a JSON string. The gap is specifically in the adapter layer, which sits between Encore and a library (Prisma) that has its own expectations about how values and type metadata arrive. Exposing column OIDs on the result closes that gap without changing any existing behavior.