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

Open
manishrjain opened this issue Feb 22, 2019 · 11 comments

Comments

6 participants
@manishrjain
Copy link
Member

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, "someone@gmail.com")) {
      v as uid
    }
  }

  mutation @if(eq(len(v), 0)) {
    set {
      <uid(v)> <name> "Some One" .
      <uid(v)> <email> "someone@gmail.com" .
    }
  }
}

This txn would check for an account with email=someone@gmail.com, 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, "someone@gmail.com")) {
      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.

@makitka2007

This comment has been minimized.

Copy link

commented Feb 24, 2019

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> "someone@gmail.com" .
    }
  }
@liqweed

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

commented Feb 24, 2019

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link

commented Feb 25, 2019

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

@srfrog

This comment has been minimized.

Copy link
Contributor

commented Feb 25, 2019

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

@srfrog srfrog self-assigned this Feb 26, 2019

@srfrog srfrog added this to Todo in Product Roadmap via automation Feb 26, 2019

This was referenced Mar 18, 2019

@srfrog

This comment has been minimized.

Copy link
Contributor

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, "someone@gmail.com"), 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 referenced this issue May 15, 2019

Merged

New upsert block #3412

17 of 21 tasks complete
@mangalaman93

This comment has been minimized.

Copy link
Contributor

commented May 16, 2019

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, "someone@gmail.com"), 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

This comment has been minimized.

Copy link

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

This comment has been minimized.

Copy link
Member Author

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> “manish@dgraph.io” .
}

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> “unique@email.com” .
  // 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

This comment has been minimized.

Copy link
Contributor

commented Jun 25, 2019

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> “manish@dgraph.io” .
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.