Prisma1: deleteMany & updateMany mutations

Created on 30 Jan 2017  路  21Comments  路  Source: prisma/prisma1

The updateAll and deleteAll mutations take a normal filter argument and applies the change to all matching nodes.

Naming

We user the plural form of the type name to indicate that a mutation can potentially affect multiple nodes:

updateManyUsers
deleteManyUsers

Example

updateManyUsers(
  filter: { name_contains: "S酶ren" }
  data: { country: "Denmark" }
) {
  count
}
deleteManyUsers(
  filter: { name_contains: "S酶ren" }
) {
  count
}

Return value

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?

Nested mutation support

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

Cascading delete support

Delete mutation will follow the onDelete cascade setting introduced in https://github.com/graphcool/framework/issues/1262#issuecomment-343773278

Transactional Semantics

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

Subscriptions

updateMany and deleteMany does not trigger subscription events.

Consider performance and recovery options for timeout

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

Most helpful comment

1 up. Very important.

All 21 comments

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

Data model

type User {
  id: Id! @unique
  name: String!
  email: String! @unique
}

1. Short version

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:

  • Why not lowercase Query.users: For certain type names like API this would lead to weird cases like aPIs
  • Input type names still depend on #1341

2. Explicit version (all)

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
}

2. Explicit version (many)

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:

3. Short version (lower case)

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.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

hoodsy picture hoodsy  路  3Comments

schickling picture schickling  路  3Comments

ragnorc picture ragnorc  路  3Comments

schickling picture schickling  路  3Comments

tbrannam picture tbrannam  路  3Comments