Support "Upsert", aka "UpdateOrCreate" mutations #8117
Replies: 13 comments
-
|
This doesn't strike me as a very common requirement.. I can only imagine this would be useful if the IDs used were somehow relevant to the item, ie. they encoded some information about it. This is pretty unusual.. normally all IDs would be arbitrary and/or random. In this case (random IDs), attempting to update an object that doesn't exist does indicate an error and shouldn't magically succeed. Am I wrong? Whats a common, real-world example of upserts being useful? Maybe restoring/reverting data in bulk..? |
Beta Was this translation helpful? Give feedback.
-
|
I used it in a couple places in Cete:
|
Beta Was this translation helpful? Give feedback.
-
|
Huh.. so the interface still "knows" whether it's creating or updating (because it has an I can imagine this causing issues with stale state in the client. Eg, the if the user opened the event in two tabs and RSVP'd in both, wouldn't you get two RSVP items being created? Maybe it would be more helpful to be able to specify filters other than the ID (almost like the Mongo "bulk" mutation {
upsertRsvp (
where: {
eventId: "262881f59fc4f3e1d7b34c99"
userId: "7e3d8a3bbda9c6309167dcc4"
}
data: {
isAttending: true
}
) {
id
eventId
userId
isAttending
}
}Not sure how this fits with OpenCRUD/Graphcool but, functionally, it's how I imagine upserts being most useful. |
Beta Was this translation helpful? Give feedback.
-
|
@molomby I like that idea! Goes perfectly with https://github.com/keystonejs/keystone-5/issues/699 |
Beta Was this translation helpful? Give feedback.
-
|
@molomby writes:
Yeah, scratch that; this would be super handy. Definitely worth separating the filters from the data being updated though. I wonder if this should fit into how we configure multi-field uniqueness in Keystone. Ie. currently, we can specify a single field is unique using the Upserts seem to be useful when you know some information about an item but maybe not whether it exists or not so, implicitly, don't have the items So... if we give app devs a way of specifying sets of fields that should be unique within the list (eg. Eg. something like this: keystone.createList('Rsvp', {
fields: {
user: {
type: Relationship,
ref: 'User',
isRequired: true,
},
event: {
type: Relationship,
ref: 'Event',
isRequired: true,
},
isAttending: {
type: Checkbox,
defaultValue: false,
},
},
// A made up, multi-field unique constraint configuration
// The object key is arbitrary; just a handle we use to name the mutation and underlying constraint
uniqueFieldSets: {
pair: ['user', 'event'],
}
});Could cause this additional GraphQL schema to be created: type rsvpPairUpsertWhereInput {
user: ID!
event: ID!
}
type rsvpPairUpsertInput {
isAttending: Bool
}
# .. all the other types
type Mutation {
upsertRsvpPair(
where: rsvpPairUpsertWhereInput!,
data: rsvpPairUpsertInput!
): rsvpOutputType
// .. all the other mutations
}There could of course be multiple sets of unique fields, resulting in multiple upsert mutations for a single list. |
Beta Was this translation helpful? Give feedback.
-
|
I think this is functionality is dependant on unique indexes which, themselves, get a lot more useful when you start dealing with compound indexing. Anyone actioning this should also be across #2304. |
Beta Was this translation helpful? Give feedback.
This comment has been hidden.
This comment has been hidden.
-
|
Seems Prisma already have the |
Beta Was this translation helpful? Give feedback.
-
|
@jesstelford Any chance that this will be available in KeyStone Next soon? Or is it available and I just didn't saw it? |
Beta Was this translation helpful? Give feedback.
-
|
@johanneshiry This isn't available for Keystone Next just yet, but we're it's on our feature list to be planned into future development. I can't give you an ETA on it just yet. |
Beta Was this translation helpful? Give feedback.
-
|
Popping in to see if there's any kind of update on this besides it being on the roadmap. I've currently side-stepped the problem by creating a function in a script to attempt and update and then insert on fail, but I'd love to have a genuine upsert mutation to work with. My use case is importing data from another system that has it's own set of IDs. I want the external IDs to be the source of truth, but I want the data in my system to make relationships easier to maintain. |
Beta Was this translation helpful? Give feedback.
-
|
I'm seeing Prisma now supports native upserts, is it planned to implement them in Keystone? https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#upsert I recall it being in the roadmap waiting for this Prisma support |
Beta Was this translation helpful? Give feedback.
-
|
Upsert / UpdateOrCreate is a common need — here are the patterns that work today while native upsert lands: Pattern 1: Query then mutate (works, but two round trips): // In your resolver or server action
async function upsertProduct(data: { slug: string; title: string; price: number }) {
const existing = await context.db.Product.findOne({
where: { slug: data.slug },
});
if (existing) {
return context.db.Product.updateOne({
where: { id: existing.id },
data: { title: data.title, price: data.price },
});
} else {
return context.db.Product.createOne({ data });
}
}Pattern 2: Raw Prisma upsert (single query, bypasses Keystone hooks): // Access the underlying Prisma client
const prisma = context.prisma;
const result = await prisma.product.upsert({
where: { slug: data.slug },
create: data,
update: { title: data.title, price: data.price },
});Pattern 3: Custom mutation via schema extensions: // keystone.ts
export default config({
extendGraphqlSchema: graphql.schema({
mutation: {
upsertProduct: graphql.field({
type: ProductType,
args: { slug: graphql.arg({ type: graphql.nonNull(graphql.String) }), ... },
resolve: async (_, args, context) => {
// implement upsert logic with hooks
},
}),
},
}),
});For data imports/syncs where you need upsert semantics at scale, Pattern 2 + Prisma is the most practical until native Keystone upsert lands. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Interestingly, these existed in GraphCool, but not in Prisma or OpenCRUD:
Greatly simplifies client code by not having to check for IDs, and switch to a different query.
In the case that you're trying to update with an ID that doesn't exist, it would throw an error the same as when doing
updatePost(id: "idontexist")Beta Was this translation helpful? Give feedback.
All reactions