prisma: Type error in `IN` clause in postgreSQL using `$queryRaw` when upgrading to v4
Bug description
There seems to be a regression regarding IN clauses in postgreSQL in prisma v4.
The issue happened on postgresql and the behaviour changed somewhere between prisma 3.15.2 and 4.2.1.
For a given table of this structure:
CREATE TABLE repro(
`id` UUID NOT NULL PRIMARY KEY,
)
When querying a list of rows using an IN query clause like so:
const rows = await this.prismaClient.$queryRaw<
{ id: string }[]
>(Prisma.sql`
SELECT id
FROM repro
WHERE id IN (${Prisma.join(ids)});
`)
This worked fine in 3.15.2 but returns a type error in 4.2.1:
{
"code": "P2010",
"clientVersion": "4.2.1",
"meta": {
"code": "42883",
"message": "db error: ERROR: operator does not exist: uuid = text\nHINT: No operator matches the given name and argument types. You might need to add explicit type casts."
}
}
I’ve tried different castings but nothing seemed to work.
This query works on the other hand:
const rows = await this.prismaClient.$queryRaw<
{ id: string }[]
>(Prisma.sql`
SELECT id
FROM repro
WHERE id = ANY (ARRAY[${Prisma.join(ids)}]::uuid[]);
`)
I haven’t seen what would cause this in the upgrade guide and it does look like a regression to me. I’m guessing there’s a change in how the casting is done which broke this query.
How to reproduce
A reproduction repository is available here: https://github.com/floriantz/repro-prisma
Expected behavior
The query should not cause an error
Prisma information
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgres"
url = "postgres://postgres:postgres@localhost:5432/postgres"
}
model repro {
id String @id @db.Uuid
}
Environment & setup
- OS: MacOS 12.2.1
- Database: PostgreSQL
- Node.js version: 16+
Prisma Version
prisma : 4.2.1
@prisma/client : 4.2.1
Current platform : darwin-arm64
Query Engine (Node-API) : libquery-engine 2920a97877e12e055c1333079b8d19cee7f33826 (at node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node)
Migration Engine : migration-engine-cli 2920a97877e12e055c1333079b8d19cee7f33826 (at node_modules/@prisma/engines/migration-engine-darwin-arm64)
Introspection Engine : introspection-core 2920a97877e12e055c1333079b8d19cee7f33826 (at node_modules/@prisma/engines/introspection-engine-darwin-arm64)
Format Binary : prisma-fmt 2920a97877e12e055c1333079b8d19cee7f33826 (at node_modules/@prisma/engines/prisma-fmt-darwin-arm64)
Default Engines Hash : 2920a97877e12e055c1333079b8d19cee7f33826
Studio : 0.469.0
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 5
- Comments: 15 (6 by maintainers)
Using the
separatorandsuffixarguments ofPrisma.joinalso appears to work. Though now that I’ve seen the map casting individual values withPrisma.sqlI find that more readable.Hey @floriantz,
Could you try casting every single uuids as follow?
Let us know whether that fixes your issue.
@Weakky Option 2 would be enough imho, but it requires to be well documented and easy to discover since it is a very common use-case of Prisma when using Postresql.
Hey folks,
This reason behind this issue is a bit complex so I’ll explain you how we arrived to this.
The need for more specific type casts was introduced in https://github.com/prisma/prisma/releases/tag/3.14.0. For context, it was introduced because we were suffering from lots of subtle and implicit type casts on PG’s side which were confusing users or even worse, preventing basic queries from working.
By default, Postgres infers the types of your query parameters based on where they are used. Here’s an example:
For this prepared statement, Postgres will answer back with a type for
$1ofTEXT(because LENGTH can only accept a TEXT input). Based on which type PG infers, the PG driver encodes query parameters values as the specified type.Unfortunately, there were many cases where Postgres could not infer the types correctly. Here’s a simple example:
This query used to fail because PG always infers the type of the query param as the type it is being cast to. So in this case,
$1was inferred as anint. Consequently, the value1.5was encoded as an integer and sent to the database like that. No need to explain you why a float encoded as an int was not working well 🙈.To circumvent that issue, we have decided to no longer let PG infer the types of query parameters and instead specify the type of every individual query parameters based on the incoming javascript values. In the example above, this means that
1.5is inferred, by Prisma, as aNUMERICtype, encoded as aNUMERICand sent to PG as aNUMERIC. Problem solved.The downside of this approach is that, in some cases, we can’t properly infer the types of your values. In fact, Prisma can only infer the following types (there might be small mistakes in this list):
In OP’s case, we’ve got a problem because Prisma cannot know that the string value is actually a uuid. So the string is encoded as a string, PG fails to implicitly cast it to a uuid and so it errors. This is the reason why it fails.
Now, we wouldn’t want to go to the old behaviour, even knowing this bug. There are only two other alternatives that I can think of to improve the situation:
The TEXT protocol sounds like our best bet, but also the riskiest one.
All that to say what? There’s technically something we can do about this, and you’re right that the DX is not great. @SevInf has already created an issue to improve documentation and that’s what we should focus on short-term.
Long-term, solutions presented above feel too costly for the attraction this bug currently has.
Based on that, here’s what I’ll do:
confirmed.kind/regressionlabel. It’s not a regression. It’s a known limitation that we knew when introducing this fix. It was clearly noted as a breaking change in the release notes.Finally, I know this comment doesn’t bring much to the table in terms of solution. But I hope it’ll give context to others coming to this issue.
Thanks
I had tried something similar, yet a bit different. It works indeed with my repro case ✅ . Though it would be super helpful to update
Prisma.join()to accept a type casting argument as proposed by @emmanuelpineda.I understand your choice of explicit types (actually I’m quite in favour of it), but the UX suffers a bit at the moment imho.
I’ve tried quite a few different things to try and cast to a uuid array, but nothing worked except this version which does not use an
INquery:Though I’d rather use IN queries. But I haven’t found a way to cast that works, I always end up with an explicit
texttype.Since these kind of queries are quite common, it might be good to have an example on how to cast those.