Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support a new Upsert operation #3059

Closed
manishrjain opened this issue Feb 22, 2019 · 12 comments · Fixed by #3412 or #3612
Closed

Support a new Upsert operation #3059

manishrjain opened this issue Feb 22, 2019 · 12 comments · Fixed by #3412 or #3612
Assignees
Labels
kind/feature Something completely new we should consider.
Milestone

Comments

@manishrjain
Copy link
Contributor

manishrjain commented Feb 22, 2019

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.

@manishrjain manishrjain added the kind/feature Something completely new we should consider. label Feb 22, 2019
@makitka2007
Copy link

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]" .
    }
  }

@liqweed
Copy link
Contributor

liqweed commented Feb 24, 2019

@makitka2007 v is empty in case of insert, not so for update. I think the @if() is flexible enough to represent both cases.

@makitka2007
Copy link

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

@srfrog
Copy link
Contributor

srfrog commented Feb 25, 2019

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.

@makitka2007
Copy link

nope, in first case you are checking for eq(len(v), 0)

@srfrog
Copy link
Contributor

srfrog commented Feb 25, 2019

You're right, then I'm not sure what that means.

@srfrog srfrog self-assigned this Feb 26, 2019
This was referenced Mar 18, 2019
@srfrog
Copy link
Contributor

srfrog commented Mar 28, 2019

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 dgraph-io/dgo#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 $.

@mangalaman93 mangalaman93 assigned mangalaman93 and unassigned srfrog May 13, 2019
@mangalaman93 mangalaman93 mentioned this issue May 15, 2019
21 tasks
@mangalaman93
Copy link
Contributor

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.

@insanitybit
Copy link

insanitybit commented May 16, 2019

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.

@manishrjain
Copy link
Contributor Author

manishrjain commented May 16, 2019

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).
}

@mangalaman93 mangalaman93 changed the title Support a new txn operation Support a new Upsert operation Jun 10, 2019
@mangalaman93 mangalaman93 reopened this Jun 25, 2019
@mangalaman93
Copy link
Contributor

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]” .
}

@manishrjain
Copy link
Contributor Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/feature Something completely new we should consider.
6 participants