New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Be able to update or retrieve a single record including non-unique fields in the "where" conditions. #7290
Comments
The same goes for |
You should use |
But if I'm using a unique field along with the other non-unique fields, it won't potentially affect multiple entries, right? Like I mentioned, I would use those, except they don't return the updated records, just an updated count. |
To be clear, my intention is to only update the singular record with the primary key field, I just want to add an additional condition to the field to make sure someone who shouldn't have permission to update the "client" won't be able to. |
Couldn't you then not just add a unique index across these two fields and use that as the identifier? |
I guess that would work, it just seems superfluous to add a unique index that includes a field that is already considered unique, especially if it's a workaround for a missing feature. Adding indexes to a database isn't "free" in that adding an indexes increases disk usage. It's probably not significant enough for my current use case currently, but I could see a future use case where adding unique indexes to every combination of fields I need to conditionally update/delete would be prohibitive. |
I've stumbled across this issue a lot as well. |
What is the use case for adding another field, if you already have a unique identifier that you know uniquely identifies 1 specific row? |
@janpio I use the additional filter as a way of further qualifying the operation. For example, an API request comes in to update a Todo item. I have the unique id of the todo from the parsed URL, and I have the current user from the auth token. I want to send a single update command to the database where the id is the given todo id and the owner of the todo is the current user’s id. This way, if someone tries to update a todo item they are not owner of, the update returns null. |
@janpio Because you might not want the query to return that row if it doesn't fit the additional criteria. |
I have the same problem. Two use cases that come to mind: maybe there is some kind of "Soft-Delete-Strategy" in place or an update is only "allowed" as long as a version field hasn't changed. The problem with the |
Exactly! Especially since that is the recommended alternative to long running transactions.
From a pragmatical standpoint that might be "the only" problem, but conceptually if you're trying to update a single record then you shouldn't use the |
It would be useful to use this feature for Optimistic Concurrency Control #4988
|
@janpio another very compelling reason to support this change is to make the recommended soft-delete middleware far less hacky. Currently the recommendation is to overwrite
Supporting non-unique where clauses on singular resource reads/updates/deletes (as is very typical and provided by default in many databases) should relieve the middleware of the responsibility of swapping out |
Another downside with this workaround: |
Did we ever find a solution to this?
Would be nice to leverage this to confirm user owns the TODO object. |
I've been looking for a way to upsert with multiple where conditions, I guess it is not supported yet ?
|
I too require this function. My use case is a user can request a relationship with another user (e.g. trying to "friend" another user). When the second user tries to accept the request, I want to make sure there's a pre-existing record in the pivot table with the status currently at "REQUESTED" instead of "BLOCKED" or some other possible status. |
This comment was marked as spam.
This comment was marked as spam.
Definitely required. For the workaround we are first querying the record and see if that meet the conditions and then calling update function with the matched id. So, why not do that in single update query ? Well, it could be a updateFirst as well |
I have a unique constraint on several columns However, the Prisma client's types will not allow me to run a .delete() query using these three fields, so I have to resort to raw queries to actually delete specific rows from this table.. |
This comment was marked as spam.
This comment was marked as spam.
Agree with everyone else here - is very odd that this behaviour isn't supported. It also appears updateMany actually just does an update on ID, but first runs a select statement to get the ids.
Generates
Where handwritten sql would be
Is there a reason for this? Is it to normalise the queries across different database types? Or is it related to the fact Prisma will only update based on unique's? |
it's limiting but also it's powerful. If updates are done by unique ids only, it opens doors to many automation around it. E.g. DynamoDB has limitation at database layer to be able to update/delete rows only if you give exact unique identifier to the row, no mass updates/removes and it's the reason why they are able to do something like seamless cache layer w/o any api modifications. You can also easily pipe updates/deletes to some queue/stream and have exact flow of events and ability to re-create state of data. etc. etc. update/delete should update delete only and only single item by it's unique identifier. in sql you have option to mass delete/update, that's why there are updateMany and deleteMany. If you don't like result of *Many functions, you can talk about having options for that call, but i would argue to keep update/delete limited to single item update/delete only no matter what. |
@liesislukas You're right, it is super powerful, but the argument that I'm making isn't that the Here's a real example that has severe security implications: In the OAuth2.0 spec in the authorization flow where a user delegates permission to a 3rd party application, when the user grants access, the authorization server generates a one-time use auth code to give to the 3rd party application. The 3rd party application will exchange that auth code in addition to its credentials to get an access token. The prisma model for the auth codes might look something like this: model AuthorizationCode {
code String @id
user User @relation(fields: [userId], references: [id])
userId String
client ClientApplication @relation(fields: [clientApplicationId], references: [id])
clientApplicationId String
createdAt DateTime
expiresAt DateTime
scope String
consumedAt DateTime?
} When the 3rd party application makes the call with the auth code, we want to in the same database call fetch the auth code and set the const now = new Date()
const authCode = await prisma.authorizationCode.update({
where: {
authCode: req.body.code, // This is the unique field and should be required
clientApplicationId: req.body.client_id, // not unique, but helps make sure clients don't consume other auth codes
expiresAt: { gt: now }, // Make sure we don't touch an expired auth code
consumedAt: null // Make sure we don't touch an auth code that has already been consumed
},
data: {
consumedAt: now // Update consumedAt so it can't be consumed again
}
})
if (!authCode) return res.status(400).json({ error: 'invalid_code' }) Because this kind of update query is not possible, it turns into something like this: const now = new Date()
const authCode = await prisma.authorizationCode.findFirst({
where: {
code: req.body.code,
clientApplicationId: client.id,
expiresAt: { gt: now },
consumedAt: null
}
})
if (!authCode) return res.status(400).json({ error: 'invalid_code' })
await prisma.authorizationCode.update({
where: { code: req.body.code },
data: { consumedAt: now }
}) This change not only makes two database calls where one would be fine, but now there is a gap of time between when we fetch the auth code (and check whether or not it has been consumed or not) and when we update the consumedAt field. This would allow for a Reply attack. The security consideration for this is briefly described here: https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/#security-considerations |
This is the workaround we currently use, based on
prisma.person.update({
data: { id: input.personId },
where: {
clients: {
update: {
where: { input.clientId }, data: { name: input.name }
}
}
},
select: {
clients: {
where: { id: input.clientId }
}
}
}).then((data) => data.clients[0]) ...and there you go. A little convoluted but does the job. The select/then part is actually really important, b/c otherwise you will get back a person object instead of a client. Of course, there is no solution available for the |
@ksmithut Totally agree. It should be allowed to provide an extra non-unique field along with an unique one in the Let me provide an example from my app. I need to mark a transaction as "finished" and then apply the transaction amount to user's balance.
But when I do this, in order to avoid double balance change I must check first that transaction is not finished by another process. I do this as suggested on Prisma docs by checking
But now the problem is that since I've changed I have all kinds of similar issues because of this limitation. I'm developing an app that works with money and I must pay close attention to race conditions. Please change this. |
This comment was marked as spam.
This comment was marked as spam.
1 similar comment
This comment was marked as spam.
This comment was marked as spam.
I think this thread has uncovered a very real use case that is currently not well supported by Prisma. We want the The In the Prisma API, it could look like this:
Conceptually, the Eventually, this could be expanded to support more complex use cases than just evaluating columns on the row to be updated. This idea is explored in some detail in a long conversation last year: #1844 (comment) |
We have encountered this problem with soft delete that we worked around using two queries. But we are wondering how do people handle authorization checks with Prisma in the end? If you have a multi-tenant application and the concept of organization and a Should all our endpoints have this kind of code then?
Any suggestions? |
This comment was marked as spam.
This comment was marked as spam.
3 similar comments
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
This comment was marked as spam.
Dear Prisma developers, let me know please if investing some money in your project will help to speed up adding this feature. We need it very much for the project that is already in production. Thank you. |
dynamodb has same issue and resolve it with related dynamo docs: await db
.update({
TableName: "some_table",
Key: {
authCode: req.body.code,
},
ConditionExpression:
"attribute_not_exists(consumedAt) and #clientApplicationId = :clientApplicationId and #expiresAt > :now ",
UpdateExpression:
"set #consumedAt = :now",
ExpressionAttributeNames: {
"#clientApplicationId": "clientApplicationId",
"#expiresAt": "expiresAt",
},
ExpressionAttributeValues: {
":now": Date.now(),
":clientApplicationId": req.body.client_id,
},
})
.promise(); |
I just realized what if we give prisma what it needs for the What if we use a unique
|
@oyatek That's a good way to handle conflicts, similar to a version field like you mentioned. But there are a lot of other cases, such as making sure only an owner can update a post. The owner field cannot be unique because the owner can own multiple posts. |
My exact problem here. It just seems like such a base level feature although I am sure it probably is not to implement without breaking other things in their codebase. While I respect the heck out of all the work the team is doing, I feel like the community is being ignored here. This was opened on May of last year and since I posted, I keep getting notifications showing so many others pointing out this flaw. And that doesn't even account for the several comment threads elsewhere. |
Then you can make a unique index based on 2 fields:
UPDATE |
Currently it isn't possible to do something like: prisma.project.delete({
where: {
id: 'foo',
userId: 'bar'
}
} To make sure that the user can only see the project that itself owns. The only workaround I can find involves adding an additional unique index, and query on that: prisma.project.delete({
where: {
id_userId: {
id: 'foo',
userId: 'bar'
}
}
} Surely we shouldn't be required to add additional unique constraints on combinations with And it also doesn't work at all for ensuring things only get deleted once, since prisma.project.update({
where: {
id_userId_deletedAt: {
id: 'foo',
userId: 'bar',
deletedAt: null
}
},
data: {
deletedAt: new Date()
}
}
|
This comment was marked as off-topic.
This comment was marked as off-topic.
@janpio another use case here is is for data security and compliance - we need to ensure that all queries where relevant are bound by the parent organization. prisma.someDao.findUnique({
where: {
id: 'someUniqueId', // <--- yes this will find one
parentOrg: 'parentOrgThatOwnsSomeUniqueId' //<--- but for compliance we need to ensure that the id belongs to the requesting organization
}
}) UPDATE: adding The first error if you attempt to provide both arguments without inspecting the types carefully is:
And upon further inspection, prisma.someDao.findUnique({
where: {
id_parentOrg: {
id: 'someUniqueId',
parentOrg: 'parentOrgThatOwnsSomeUniqueId',
}
}
}) So in conclusion (for our needs) this is good enough, but likely worth some docs to clarify further. |
this obvious easy thing should be allowed. How come prisma does not support this? Please bring this in... |
+1 to supporting a updateOne() similar to findOne() based on id and attribute/where clause |
Hey folks, A solution was implemented by prisma/prisma-engines#3281. It will be available in the next release under a new preview flag called If you have any feedback you'd like to give about this feature, please post it here #15837 (comment) Thanks for your patience, closing now 🙏. |
Problem
I am unable to utilize non-unique fields in single update queries. To be clear, I want to still use the unique fields, I just want to filter it down even more.
Suggested solution
Here are my models
I have "people" who have "clients" attached to them. Only the person who owns the client can update the name. So when an API call comes in, I know who the current person is and what client they're trying to update. So the update query I would like to use is this:
but it only allows fields which are marked as
@unique
or@id
Alternatives
One alternative is to do a
.findUnique({ where: { id: input.clientId } })
and then check if the personId of the client is the same as the one passed in. This however creates two database calls where only one is needed.Another alternative is to do a
.updateMany({ where: { id: input.clientId, personId: input.personId } })
but I don't get any of the clients back. If I got the clients back in the query and if there was alimit
I could pass in to limit it to one, I would feel better about this so it wouldn't have to do any unneeded scans of the rows, but it still feels less idiomatic than updating the.update()
command to allow for non-unique fields.The text was updated successfully, but these errors were encountered: