Skip to content

runtimes/js (sqldb): expose column metadata (OID, type name) on rawQueryAll results#2417

Open
shimonenator wants to merge 1 commit intoencoredev:mainfrom
shimonenator:add-column-metadata
Open

runtimes/js (sqldb): expose column metadata (OID, type name) on rawQueryAll results#2417
shimonenator wants to merge 1 commit intoencoredev:mainfrom
shimonenator:add-column-metadata

Conversation

@shimonenator
Copy link
Copy Markdown
Contributor

@shimonenator shimonenator commented Apr 30, 2026

Context

I am building a Prisma driver adapter that lets Encore.ts SQLDatabase instances be used as the underlying connection for Prisma ORM. The main objective is to enable tracing and make debugging easier, as queries sent via regular prisma-pg adapter go invisible in the dashboard.

Prisma's driver adapter interface requires each query result to include a columnTypes array alongside columnNames and rows. The columnTypes values come from Prisma's ColumnTypeEnum and tell Prisma how to interpret each value before handing it to the application — for example, whether to call JSON.parse, whether to convert to BigInt, or whether to pass the value through as-is.

Without type metadata from the driver, the adapter has no reliable way to populate columnTypes correctly. 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:
Screenshot 2026-04-20 at 4 08 29 PM
Screenshot 2026-04-20 at 4 10 01 PM


The Problem: JSONB Without Type Metadata

PostgreSQL's JSONB type 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:

  • A jsonb column returning [1, 2, 3] looks identical to an int4[] column returning [1, 2, 3]
  • A jsonb column returning "hello" looks identical to a text column returning "hello"
  • A jsonb column returning { "key": 1 } looks identical to any other column returning a plain JS object

Without 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 Json schema fields using template literal coercion: `${value}`. For a plain JS object this produces a valid JSON string. For a JS array it does not:

`${[1, 2, 3]}` → "1,2,3"   // not valid JSON

Prisma then calls JSON.parse("1,2,3") which throws. The result: a findMany on a model with a Json field 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 string hello. Prisma's template literal gives:

`${"hello"}` → "hello"     // not valid JSON
JSON.parse("hello")        // throws SyntaxError

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 to JSON.stringify the 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), and typeName available on the result, the adapter can:

  1. Return correct ColumnTypeEnum valuesjsonb (OID 3802) maps to ColumnTypeEnum.Json; _jsonb (OID 3807) maps to ColumnTypeEnum.JsonArray; int4[] (OID 1007) maps to ColumnTypeEnum.Int32Array. Prisma uses this to apply the correct deserialization path.

  2. Normalize values correctly per column — For jsonb columns, the adapter can JSON.stringify arrays and bare strings before returning them, so Prisma's template literal coercion produces valid JSON that JSON.parse can consume.

  3. Resolve column identity in JOIN resultstableOid identifies 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 null tableOid since they are not associated with a specific table.

  4. 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 standard pg Node.js driver via field.dataTypeID and field.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 rawQueryAll directly — 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.

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

1 participant