Upsert for uniqueness, how to know if the Cond blocked the Mutation?

Hi,

I need to ensure that some fields of my types are unique.
Like for example the email of a user.

I used the Upsert Block for this. And it’s working.
Only when the email doesn’t exist a new User is added to the db.

But how can I now if a user was created or not?
I see no field in the response directly telling me this. I can just guess if the affteced uids is bigger then 2, it seems to be success otherwise the upsert query condition blocked the mutation.

my upsert block:

query := fmt.Sprintf(`
	query {
		user as var(func: eq(email, %s))
	}
	`, *user.Email)

	mu := &api.Mutation{
		Cond: `@if(eq(len(user), 0))`,
		CommitNow: true,
		SetJson: pb,
	}
	
	request := &api.Request{
		CommitNow: true,
		Query:     query,
		Mutations: []*api.Mutation{mu},
	}
	
	response, errC := GetDB().NewTxn().Do(ctx, request)

thank you

Query again. I have come up with this question years ago and the answer was pretty much this one I’m giving. It is fine tho.

Also, an Upsert only show the “present” version of the data. New data it won’t return in the same transaction. You have to create another one.

interesting,
can’t I just use the dgraph-toucheduids key from the response?
as the user creation will affect some more data, that number should be higher then?

but instead of query again, I could also do a query first, check the result and then decide to either execute the mutation or not?
Then upsert is not needed, and it’s one query less.

Yes, but you need to know which one they are. So a new query is needed. Or you have to identify them in your app.

Not sure, you have to test it out. Touched UIDs was created to use in the GraphQL context if I’m not mistaking. Not sure how useful for your case it is.

No, Just create a very precise Upsert Query and trust on it. Do several tests to see the behavior of your query. And then just trust it.

If you wanna uniqueness and less transactions, Upsert Block is very useful.

hm can’t I just do the query in a transaction so it is similar to upsert but I know the result?
If I try to check it afterward with a query it could be difficult.

Let’s assume the only thing I need to register for a new user is the email.
In case another user is already registered with that email I don’t know if the new registration was successful as in both cases (insert or blocked) I will find a user with this email.
I could add an additional parameter like the registration time to check it. But doing it like this. Would in the end also be the same as just query before the mutation
As in theory between query and mutation, another mutation for the same email address could happen. But this would also be the case if I query afterwords.

Or did I completely understand something wrong?

That hypothesis is invalid for a new account cuz two different person can’t have the same email from the same service provider. Let’s assume a third person have stolen Bob’s email and somehow he was able to create an account in your service. And then the real Bob come back and try to create an account with the same email. Well, if Bob have recovered his email. Your job is to offer him a way to recover that account too. You choose the strategy. Send him a recover method.

Ignoring that, if Bob tries to create an account with an existing account that he doesn’t remember exists. Your upsert will be able to tell that the account exists and ignore. So, if there is an account, the upsert will not execute and return “Done” and the values at the query request. Meaning that the user already exists. But if the user doesn’t exists, it will create one return “Done” along with the created UIDs in the UID key of the response. e.g:

{
  "data": {
    "q": [], # This is empty cuz there was no data before
    "code": "Success",
    "message": "Done",
    "uids": {
      "uid(v)": "0x1" # This returns the UID of the new node created.
    }
  },
  "extensions": {...}
}

ok, email wasn’t the best example. It was more like that. Bob registered 1 month ago. Then he forgot he is registered and start a new registration. (Happens many times)

but based on your answer that’s what I need: if the mutation was executed I get the uid back. if not uids are empty.
(I think the response from your example is if you do an RDF request. But I do a JSON request)
I checked what I get in api.Response and there Response.Json is empty. there is nothing in.
But at least Response.Uids works :slight_smile:
Thanks again

could it be a bug in the GO client that the Json representation in the api.Response is empty?
See my example code at the beginning

The query response will be empty cuz this query is empty. It should be like:

query {
		user as var(func: eq(email, %s)) {
                     UID
                     email
                     name
              }
	}

That way you will have responses in the query response key.

hm, I changed it as you suggested. but the response is the same:

that is the response when the user is new:

json:"{}" txn:<start_ts:12570 commit_ts:12571 preds:"1-\000\000\000\000\000\000\000\000auth_id" preds:"1-\000\000\000\000\000\000\000\000dgraph.type" preds:"1-\000\000\000\000\000\000\000\000email" preds:"1-\000\000\000\000\000\000\000\000last_login_at" preds:"1-\000\000\000\000\000\000\000\000name" preds:"1-\000\000\000\000\000\000\000\000nationality" preds:"1-\000\000\000\000\000\000\000\000registered_at" preds:"1-\000\000\000\000\000\000\000\000surname" preds:"1-\000\000\000\000\000\000\000\000updated_at" > latency:<parsing_ns:202100 processing_ns:4059300 encoding_ns:11700 assign_timestamp_ns:1113400 total_ns:5766100 > metrics:<num_uids:<key:"" value:1 > num_uids:<key:"_total" value:12 > num_uids:<key:"auth_id" value:0 > num_uids:<key:"email" value:0 > num_uids:<key:"mutation_cost" value:10 > num_uids:<key:"uid" value:0 > num_uids:<key:"user" value:1 > > uids:<key:"secret_id_1" value:"0x2712" > hdrs:<key:"content-type" value:<value:"application/grpc" > > hdrs:<key:"dgraph-toucheduids" value:<value:"12" > >

and this is the response when the user tries again:

json:"{}" txn:<start_ts:12576 commit_ts:12577 > latency:<parsing_ns:188700 processing_ns:2645200 encoding_ns:16300 assign_timestamp_ns:774300 total_ns:3788000 > metrics:<num_uids:<key:"" value:1 > num_uids:<key:"_total" value:5 > num_uids:<key:"auth_id" value:1 > num_uids:<key:"email" value:1 > num_uids:<key:"mutation_cost" value:0 > num_uids:<key:"uid" value:1 > num_uids:<key:"user" value:1 > > hdrs:<key:"content-type" value:<value:"application/grpc" > > hdrs:<key:"dgraph-toucheduids" value:<value:"5" > >

so in both cases, JSON is empty.
but the map of UIDs is filled in case it was a new user. (so the upsert query didn’t stop the mutation)

btw. in your documentation: upsert example the upsert query is also empty.

That looks normal to me. Not sure what you mean. Maybe this is related to the Go Client only? What exactly is empty? Test it in Ratel first.

So you did something wrong. I’m 100% that it should ignore if the user exists.

this was related to your post:

the query in the documentation is the same as mine. but you said that mine is wrong as I didn’t select any fields to return.

that’s what I meant: it ignores the user if it exists:


The json in the response from the request is empty:

Ahhhhhhh! Sh*t! I just forgot about the VAR block. Var blocks don’t appear in any query response… give it a name so it will appear. Sorry!