The updateAll and deleteAll mutations take a normal filter argument and applies the change to all matching nodes.
We user the plural form of the type name to indicate that a mutation can potentially affect multiple nodes:
updateManyUsers
deleteManyUsers
updateManyUsers(
filter: { name_contains: "S酶ren" }
data: { country: "Denmark" }
) {
count
}
deleteManyUsers(
filter: { name_contains: "S酶ren" }
) {
count
}
As these mutations can affect an unbounded number of nodes, it is not feasible to return a list of all affected nodes. Instead a count of the affected nodes is returned.
To decide: does the count return the number of nodes matching the filter or the number of nodes actually updated because their value was different?
In the initial implementation, nested mutations will not be supported. This means that updateAll can only modify scalar values on the parent type. There is no way to change relations of fields on related types
Delete mutation will follow the onDelete cascade setting introduced in https://github.com/graphcool/framework/issues/1262#issuecomment-343773278
To decide: Should we provide transactional guarantee for updateMany and deleteMany mutations? Mostly relevant for situations where the cascade configuration can make the deleting of a single node fail, forcing a complete roll back
updateMany and deleteMany does not trigger subscription events.
These mutations are synchronous. When operating on millions of nodes, it is possible that the operation takes longer than the configured http timeout. If this happens, the operation will continue in the background and there will be no way for the process that initiated the operation to get a status update or cancel the operation
1 up. Very important.
For those who land here, this is a script that I execute locally to remove old log
messages from a log
table.
const logsQuery = `
query getLogs {
logs: allLogs(first: 100, orderBy: timestamp_ASC) {
id
}
}`
const logsDeleteQuery = `
mutation deleteLog(
$id: ID!
) {
deleteLog(id: $id) {
id
}
}`
let count = 0
function deleteAll() {
console.log('Fetching new Events')
return Graph(logsQuery).then((res) => {
console.log(res)
if (res && res.logs.length > 0) {
count = count + res.logs.length
console.log('Deleting ' + res.logs.length + '(' + count + ') events...')
return Promise.all(res.logs.map(l => {
console.log('Deleting event ' + l.id)
return Graph(logsDeleteQuery, {id: l.id})
}))
} else {
console.log('finished!')
}
})
.then((res) => deleteAll())
.catch((e) => console.log(e))
}
It basically iterates over the data and then deletes all items one by one. It does so in batches of 100.
The Graph
call in this snippet is a stub for executing a graphql query (first argument) with the given query params (optional second argument) against some graphcool endpoint. This call returns a promise.
@ejoebstl what is Graph
?
@sedubois I've updated my original comment to explain the Graph
call. TL/DR: It's a stub so you don't have to care which specific client I use to connect to the endpoint.
+1 for sure for updating.
Let's say you have a list of invoices from one client. You want to mark them as paid.
Currently you have to loop and ensure on the client that all are updated which is really cumbersome. In SQL you would just do something like:
UPDATE invoices SET paid = true WHERE ID IN (id1, id2, id3 ....)
any news on this?
It's currently planned to include this functionality in the graphcool-lib module.
Hey mates! Which is the state of this issue? : )
Any news about batch update/delete?
One more in the line for this !
@alexvcasillas This is planned to be included in Graphcool 1.0 as described in https://github.com/graphcool/framework/issues/353
@schickling @nikolasburk @marktani I would like your input on the naming question so we can lock this spec
@ejoebstl @kbrandwijk @dpetrick I would like your take on the question of transactional support for these mutations. My current take is that they should not by default be transactional, and we can in the future introduce support by adding the @transactional
directive
I prefer updateAllUsers
, as it aligns well with allUsers
.
@schickling @nikolasburk @marktani I would like your input on the naming question so we can lock this spec
type User {
id: Id! @unique
name: String!
email: String! @unique
}
type Query {
Users(filter: UserFilter): [User!]!
User(by: UserBy): User
}
type Mutation {
createUser(data: UserCreateData): User!
updateUser(data: UserUpdateData, by: UserBy): User
deleteUser(by: UserBy): User
updateUsers(data: UserUpdateData, filter: UserFilter): BatchPayload
deleteUsers(filter: UserFilter): BatchPayload
}
Notes:
Query.users
: For certain type names like API
this would lead to weird cases like aPIs
type Query {
allUsers(filter: UserFilter): [User!]!
findUser(by: UserBy): User
}
type Mutation {
createUser(data: UserCreateData): User!
updateUser(data: UserUpdateData, by: UserBy): User
deleteUser(by: UserBy): User
updateAllUsers(data: UserUpdateData, filter: UserFilter): BatchPayload
deleteAllUsers(filter: UserFilter): BatchPayload
}
type Query {
manyUsers(filter: UserFilter): [User!]!
findUser(by: UserBy): User
}
type Mutation {
createUser(data: UserCreateData): User!
updateUser(data: UserUpdateData, by: UserBy): User
deleteUser(by: UserBy): User
updateManyUsers(data: UserUpdateData, filter: UserFilter): BatchPayload
deleteManyUsers(filter: UserFilter): BatchPayload
}
Notes:
It's quite a common pattern that you're doing something like the following in your code. Having findUser
instead of User
can quickly add an overhead:
// short version
const { User } = client.query(`{ User(by: { id: "my-id" }) { id name } }`)
console.log(User.name)
// explicit version
const { findUser } = client.query(`{ findUser(by: { id: "my-id" }) { id name } }`)
console.log(findUser.name)
// explicit version ("fixed" with alias)
const { user } = client.query(`{ user: findUser(by: { id: "my-id" }) { id name } }`)
console.log(iser.name)
Thoughts on 1 vs 2?
I prefer the more explicit version 2. Might be personal taste but I'm not a fan of mixing uppercase/lowercase fields on GraphQL types - to my perception it's a convention to lowercase fields on GraphQL types and I think we should stick to that.
I don't have a strong opinion on the updateAll
vs updateMany
question.
Interesting point! I still favor the more explicit findUser
- also because destructuring only targets JS. Plus, if people are using schema stitching/transformation, they will be able to rename the exposed fields for their convenience anyways. I'd rather favor that our API is consistent and adheres to conventions rather than trying to accomodate the convenience use case for destructuring results in JS.
Maybe using just user(by: UserBy): User
might actually be an option? I get your point about potential naming issues with model types that are all uppercase like API
. But even here I think the better solution would be to call the type Api
to avoid having the aPI
field on Query
.
I am also a strong proponent of version 2, mainly for the same reasons @nikolasburk mentioned. However, not because of some naming convention for lowercase field names, because there is no such convention, but because of the more explicit nature and the final schema transformation possibilities a gateway offers.
Great point @nikolasburk I like that. Given #1342 this should be possible yielding the following API:
type Query {
users(filter: UserFilter): [User!]!
user(by: UserBy): User
}
type Mutation {
createUser(data: UserCreateData): User!
updateUser(data: UserUpdateData, by: UserBy): User
deleteUser(by: UserBy): User
updateUsers(data: UserUpdateData, filter: UserFilter): BatchPayload
deleteUsers(filter: UserFilter): BatchPayload
}
Is everyone okay with this proposal?
Sounds like proposal 3 has legs. I'm speccing out the entire API surface in https://github.com/graphcool/framework/issues/1340 Please continue further naming discussion there :-)
We have moved back to the updateManyUsers
terminology from @schickling's proposal 2
This feature is now available as part of stage beta2 of the developer preview for Graphcool 1.0.
Most helpful comment
1 up. Very important.