Mutate multiple models at once
Sure, I'm a little busy but I can help a little after work hours.
What problem does this feature proposal attempt to solve?
Some extra mutation directives that would cut down on the amount of db calls (eg. rather than passing in X number of @create/@delete mutations in a single request, merge them into a single mutation using @createMany/@deleteMany
@updateOrCreate can be useful when for when there is a unique key based on 2 or more columns. For example, in my case a there is an intermediary table which combines jobs to candidates, so [job_id, candidate_id] is the unique key, meaning that if you try to pair them together more than once on the intermediary table youll get an error back from the database about them needing to be unqiue. (this might also require adding some extra prop to the relation payload for nested mutations)
@createMany/@updateMany/@upsertMany
@updateOrCreate
@deleteMany/@restoreMany
Which possible solutions should be considered?
No idea, not sure about the internals, if you let me know how you want it achieved, I can find some time after work to make a PR.
EDIT: Just noticed I'm blind and that we do have @upsert already
Could we focus this proposal on providing a way to mutate many models at once? Please describe the problem statement without suggesting a particular technical solution first.
Sure, it would be nice if there is a way to create, update, delete multiple entries in a single mutation, for example:
type Mutation {
createUsers(input: [CreateUserInput!]): [User] @createMany
}
type Mutation {
deleteUsers(input: [ID!]): [User] @deleteMany(idColumn: "id")
}
type Mutation {
updateUsers(input: [UpdateUserInput!]) @updateMany(idColumn: "id")
}
input UpdateUserInput {
id: ID!
...otherFields
}
The original issue description is still formulated partly in terms of a proposed solution. I would prefer not to shoebox our thinking and have the description be solely about what problem the issue is supposed to solve,the motivation for it.
From my understanding (still fairly limited), the way to currently store multiple database rows is to pass through a stack in mutations each containing a single rows worth of data.
I would like to be able to pass in a list of data to be able to store multiple rows in the database using a single mutation. (Following from that, it would be nice to have a way to do something similar with deleting and updating from the database as well)
Quick bump, any suggestions for tackling this problem? Thanks
I thought about reusing the existing mutation directives such as @create or @delete, since semantically the operations are equivalent. I am not sure they can just be made smart enough to recognize when multiple models should be created reliably. For example, should the following create multiple models with nested arguments, or perhaps only one model with a JSON-encoded attribute bar:
type Mutation {
createFoo(bar: [BarInput!]!): [Foo!]! @create
}
We would have to use some heuristics to decide which one to do, so I guess having explicit variants such as @createMany or @deleteMany would be the way to go.
In terms of implementation, you should be able to look at the implementations of the existing mutation directives and derive the new implementation from them. Some open questions which will have to be determined during implementation:
- should the abstract
MutationExecutorDirectiveandModifyModelExistenceDirectivebe adapted to handle multiple models? I would guess yes, perhaps being written to determine it generically or statically based on an abstract method in the implementing directive - should we wrap all model operations in a single transaction, or do one per model? how should we handle partial failure, e.g. one of the create operations fails?
- do the new directives work as nested arg resolvers?
should the abstract
MutationExecutorDirectiveandModifyModelExistenceDirectivebe adapted to handle multiple models? I would guess yes, perhaps being written to determine it generically or statically based on an abstract method in the implementing directive
I'm not familiar with these directive, I'll have to take a look. If you wish for it to work this way, I'm happy to take a look and propose some kind of implementation.
should we wrap all model operations in a single transaction, or do one per model? how should we handle partial failure, e.g. one of the create operations fails?
We could have this as a directive parameter that defaults to a complete failure but could be set to a partial failure.
Not sure what to call the parameter but for examples sake:
... @createMany(partialFailure: true)
do the new directives work as nested arg resolvers?
Still quite unfamiliar with a lot of the codebase so I wouldn't feel qualified to make a decision on this, not knowing the full benefits/drawbacks of doing so.
Let's keep it simple for now and just default to a single transaction and all-or-nothing semantics. Easier both to implement and to reason about.
do the new directives work as nested arg resolvers?
To clarify, that means they could be used like this (in this example to populate the relation Post.comments):
input CreatePostInput {
title: String
comments: [CreateCommentsInput!] @createMany
}
@create and others already work like this, I don't see a drawback to implementing this other than the added complexity.
Anyways, we don't need to plan out and discuss the implementation in detail here before it even started. I propose we deal with any further decisions and questions as they arise when actually building this feature.
@delete already accepts a list of IDs and will become even more flexible with https://github.com/nuwave/lighthouse/pull/2289.