typegraphql-prisma
typegraphql-prisma copied to clipboard
Error setting json value to null
You cannot set a nullable json value to null
To Reproduce define a json column in the prisma schema like this:
model MyTable {
id String @id @db.Uuid
myColumn Json?
}
then build graphql schema and execute the following mutation
mutation {
updateMyTable(data: { myColumn: null},
where: {id: "3e48e900-20f3-4cce-899e-f6323525645d"}) {
id
myColumn
}
}
Expected Behavior The value should be updated to null
Logs
[GraphQLError [Object]:
Invalid `helpers_1.getPrismaFromContext)(ctx).myTable.update()` invocation in
/node_modules/@generated/type-graphql/resolvers/crud/MyTable/MyTableCrudResolver.js:69:68
66 }
67 async updateMyTable(ctx, info, args) {
68 const { _count } = (0, helpers_1.transformFields)((0, graphql_fields_1.default)(info));
→ 69 return (0, helpers_1.getPrismaFromContext)(ctx).myTable.update({
where: {
id: '3e48e900-20f3-4cce-899e-f6323525645d'
},
data: {
myColumn: null
~~~~
}
})
Argument myColumn for data.myColumn must not be null. Please use undefined instead.
] {
locations: [ { line: 2, column: 3 } ],
path: [ 'updateMyTable' ],
extensions: {
requestId: '97562b3c-d5ad-4a1d-b944-3fb9f9729e82',
code: 'INTERNAL_SERVER_ERROR'
}
}
Package versions: [email protected] [email protected] [email protected]
How do you set it to null in raw prisma client?
Looks like there is a special enum value to do it: Prisma.DbNull
From Working with json fields
import { Prisma } from '@prisma/client'
prisma.log.create({
data: {
meta: Prisma.DbNull,
},
})
export const DbNull: 'DbNull'
So can you execute such mutation?
mutation {
updateMyTable(
data: { myColumn: "DbNull" },
where: { id: "3e48e900-20f3-4cce-899e-f6323525645d" }
) {
id
myColumn
}
}
Yes, that works @MichalLytek ... but it's pretty weird behavior... setting to null
seems more natural and consistent with how it works for other data types
Agree but it's Prisma's design. You can find an issue on their repo and complain about that.
All I can think of here is to introduce yet another generator option like treatNullAs = "DbNull"
so that it translates null
in query to Prisma.DbNull
.
However, it might be working incorrect because JS and GraphQL has weird behavior for null
and undefined
but will see 👀
That makes sense to me @MichalLytek. FYI, Prisma prevents null assignment through type checks (see below).
I think the issue in typegraphql-prisma is that assigning null is allowed by graphql (it's a valid value for JSON)... but you get an exception when you use it. I think setting to DbNull behind the scenes sounds like the right behavior. For now i'm implementing this middleware to handle this:
const handleNullJson: MiddlewareFn = async (resolver, next) => {
const { data } = resolver.args;
if (data.myColumn === null) data.myColumn = 'DbNull';
return await next();
};
The problem I see with the implementation of such behavior is that GraphQL-JS does not allow for input resolvers. So there's no easy way to apply such transformations. The closest to that is generating a global middleware similar to yours that will try to recursively scan query/mutation inputs and replace the values. I'm not sure what we can do with this issue. I agree that this might be inconvenient as GraphQL allow passing null for optional arguments while Prisma forbids that 😕
I think the challenge here is that most databases which support this JSON type would accept null
in the field. So to have this limitation due to a single data provider feels a bit restrictive to me and breaks the principle of least surprise
For example, I implement versioned entities which upserts the current table, then inserts into the history table. Since I'm within a transaction block, I should be able trust the data coming back from the entity.upsert
method.
// this does not work
const dto = new OrganizationEntity();
const model = await this.$transaction(async ({
organization, organization_history
}) => {
const dbo = this.mapper.toDb(dto);
const model = await organization.upsert({
create: dbo,
update: dbo,
where: { id: dbo.id || -1 } // really annoying, but if the request does not have an ID, I cannot pass undefined or null here, must be a non-nullable value
});
await organization_history.create({
data: model
});
return this.mapper.fromDb(model);
});
This forces me to map the model
back into my entity
class, then back into a prisma-compatible object even though the type definitions for both match
const dto = new OrganizationEntity();
// this works assuming the mappers handle the conversion to & from correctly
const model = await this.$transaction(async ({
organization, organization_history
}) => {
const dbo = this.mapper.toDb(dto);
const model = await organization.upsert({
create: dbo,
update: dbo,
where: { id: dbo.id || -1 } // really annoying, but if the request does not have an ID, I cannot pass undefined or null here, must be a non-nullable value
});
const result = this.mapper.fromDb(model);
await organization_history.create({
data: this.mapper.toDb(result)
});
return result;
});