This operation would allow a user to run a query, and use its results (via variables) to execute the mutation. This would then allow upserts to happen, without doing multiple network calls to the DB.
txn {
query {
me(func: eq(email, "[email protected]")) {
v as uid
}
}
mutation @if(eq(len(v), 0)) {
set {
<uid(v)> <name> "Some One" .
<uid(v)> <email> "[email protected]" .
}
}
}
This txn would check for an account with [email protected], and only if it is present, would
it run the mutation.
This should return the result of query, normally. And also return the result of the mutation.
Additionally, we should be able to optionally do the mutation, iff the email is already present.
txn {
query {
me(func: eq(email, "[email protected]")) {
v as uid
}
}
mutation @if(gt(len(v), 0)) {
set {
<uid(v)> <name> "Changed Name" .
}
}
}
This would introduce a new txn operator, and a new if directive applicable for mutations.
that would be great
one thing,
imho, blank nodes would have more sense in first case (since v is empty):
mutation @if(eq(len(v), 0)) {
set {
_:me <name> "Some One" .
_:me <email> "[email protected]" .
}
}
@makitka2007 v is empty in case of insert, not so for update. I think the @if() is flexible enough to represent both cases.
i see, but in case of insert - what does <uid(v)> mean if v is empty?
for update it's ok, i was talking about first case (insert) only
uid(v) wont be empty, that's what we are checking with @if(...) that we got an uid list len(list) > 0.
Inside the if(), v represents a set of uid values. Inside the set{} block it represents a value map, so in essence all the mutations inside the block will happen for the uid set given.
nope, in first case you are checking for eq(len(v), 0)
You're right, then I'm not sure what that means.
The first PR is ready, but blocked by API changes. The syntax changed a bit to not interfere with IRI values. Also, the query is treated as a conditional so if there are no matches and/or no vars generated the mutation won't happen. To test you need the branch in #3197 and https://github.com/dgraph-io/dgo/pull/50
Ex:
txn {
// mutation depends on this conditional query matching and yielding 'v'
query {
me(func: eq(email, "[email protected]"), first: 1) {
v as uid
}
}
// needs query above to set 'v'
mutation {
set {
// notice no angle brackets around `uid(v)`
uid(v) <name> "Changed Name" .
}
}
}
Additionally, when using dgo API for a mutation, the field CondQuery can contain the conditional query. It needs to yield a value at least. Finally, the CondQuery is treated as a root query, so it can use query vars $.
I think something like this could work too. upsert could be treated like a keyword like var. We can potentially have more queries in the block. We will only allow one upsert query in a block
{
me(func: eq(email, "[email protected]"), first: 1) {
v as uid
}
upsert {
uid(v) <name> "Changed Name" .
}
}
If there are more than 0 values in v, then, we will do a simple update mutation. If not, in the case, we will generate new UIDs and run the mutations.
Just for my clarification, will this allow me to use a single round trip to perform multiple unrelated transactions?
I have a use case where I receive N node 'descriptions', and I need to create or update. Right now I use a single transaction for all of them - but this means that if any fails, it's treated as a total transaction failure.
I want each node create/update to be its own transaction, with only a single round trip.
Based on the syntax this doesn't seem like it's the case, because the upsert statement is not tied to individual query statements.
Notes from discussion:
Query {
m as myself from email id.
f as Twitter followers
}
Mutation @if(gt(len(m), 0)) {
// If you found ME, then create at least one friend. Could be a blank node.
<uid(m)> <friend> <uid(f)> .
}
Case 1: INSERT: Create myself
mutation @if(eq(len(m), 0)) {
_:me <name> “Manish” .
_:me <email> “[email protected]” .
}
Case 2: UPSERT: Found myself, found followers. Connect the two.
mutation @if(eq(len(m), 1) AND gt(len(f), 0)) {
<uid(m)> <friend> <uid(f)> .
}
Case 3: UPSERT + INSERT: Found no followers, but myself. Create a friend.
mutation @if(eq(len(m), 1) AND eq(len(f), 0)) {
_:friend <name> “friend” .
<uid(m)> <friend> _:friend .
}
// Find a user.
// If found: Then update, name.
// If not found, then update name.
Case 4:
mutation {
<uid(m)> <name> “user” .
<uid(m)> <email> “[email protected]” .
// Dgraph would replace uid(m) with a blank node if no results for uid(m).
}
Should we support multiple mutations in one request? For example -
Query {
m as myself from email id.
f as Twitter followers
}
mutation @if(eq(len(m), 1) AND eq(len(f), 0)) {
_:friend <name> “friend” .
<uid(m)> <friend> _:friend .
}
mutation @if(eq(len(m), 0)) {
_:me <name> “Manish” .
_:me <email> “[email protected]” .
}
Depends upon the internal code complexity. It is marginally more useful than just having one mutation block. But, if the code complexity is disproportionately large, then probably not for the first cut.
Most helpful comment
Notes from discussion: