Dgraph raw upsert in javascript http client

Experience Report for Feature Request

I wanted to send raw upsert query to Dgraph using the Javascript HTTP client

I had to instead break up the raw query into the ORM-like objects.

Why can’t you just pass the upsert to dgraph with the js-http client? Why instead do I have to break it out into the query and mutation parameters and use the setSetJson/setDeleteJson?

const request = new Request();
request.setQuery(`{
  foo as var(func: type(Foo)) @filter(not has(name))
}`);
const mutation = new Mutation();
mutation.setSetNquads(`uid(foo) <name> "Foo" .`);
request.setMutationsList([mutations]);
request.setCommitNow(true);
const txn = DgraphClient.newTxn();
txn.doRequest(request)
// ...

vs:

const request = new Request();
// this method does not exist!
request.setUpsert(`upsert {
  query {
    foo as var(func: type(Foo)) @filter(not has(name))
  }
  mutation {
    set {
      uid(foo) <name> "Foo" .
    }
  }
}`);
const txn = DgraphClient.newTxn();
txn.doRequest(request);
// ...

I feel like this is equivalent to a poor ORM that no longer lets me send a full query when I need to do so.

Any external references to support your case

Hey @amaster507

I just found that we can do this via dgraph-js-http.
After that conversation we had I went to see if I had done anything related to this. And I found it.
What I did was.

 const upsertString = ` upsert {
            query {
               .....
            }

            mutation  {
              delete {
                .....
              }
            }

            mutation  {
              set {
                .....
              }
            }
          }
      `;

await dgraph.mutate(upsertString); # This is my own method. Not related to dgraph-js-http
# Inside my method/class I did
try {
(...)
if ((typeof mutation !== 'object') && mutation.includes("upsert")) {
        const response = await txn.mutate({ mutation: mutation, commitNow: true });
        return response;
      }
(...)
      return response;
    } catch (e) {
      console.error('Error: ', e);
    } finally {
      await txn.discard();
    }

This works. I was using in some code of a customer(mine).

1 Like

I’ll give this a whirl.

EDIT:

That did not work. :frowning:

{
  error: TypeError: c[e].toArray is not a function
      at Function.jspb.Message.setRepeatedWrapperField (/home/node/app/node_modules/google-protobuf/google-protobuf.js:552:180)
      at proto.api.Request.setMutationsList (/home/node/app/node_modules/dgraph-js/generated/api_pb.js:729:23)
      at Txn.<anonymous> (/home/node/app/node_modules/dgraph-js/lib/txn.js:136:21)
      at step (/home/node/app/node_modules/dgraph-js/lib/txn.js:44:23)
      at Object.next (/home/node/app/node_modules/dgraph-js/lib/txn.js:25:53)
      at /home/node/app/node_modules/dgraph-js/lib/txn.js:19:71
      at new Promise (<anonymous>)
      at __awaiter (/home/node/app/node_modules/dgraph-js/lib/txn.js:15:12)
      at Txn.mutate (/home/node/app/node_modules/dgraph-js/lib/txn.js:131:16)
      at Dgraph.doUpsert (/home/node/app/libs/api/dgraph/src/services/Dgraph.ts:173:28),

The last line is our extended Dgraph class where I added the doUpsert method like you posted above:


  async doUpsert(upsert: string): Promise<Response> {
    if (!upsert.startsWith("upsert")) {
      const error = 'Upsert must start with "upsert".'
      this.logger.error(error, { data: { upsert } })
      throw new Error(error);
    }
    const txn = this.newTxn(); // this is dgraph-js default DgraphClient.newTxn
    let response: Response;

    try {
      // type casting needed because `mutation: string` is not in the Mutation class ??
      response = await txn.mutate({ mutation: upsert, commitNow: true } as unknown as Mutation);
    } catch (error) {
      throw error;
    } finally {
      await txn.discard();
    }
    return response;
  }

Not a big deal, I will just do it the other way. Just wanted to update this to see if you see an error anywhere in my snippets?

This error happens when using Dgraph-JS, switch to Dgraph-JS-http.
There is no gRPC in the HTTP client.

Here is a complete copy of the class.
Pay attention that this is a custom stuff. It needs improvement.

import { DgraphClient, DgraphClientStub} from 'dgraph-js-http';

class DgraphService {
  constructor() {
    const stub = new DgraphClientStub('http://192.168.9.163:8081/');
    this.client = new DgraphClient(stub);
  }

  async query(q) {
    const response = await this.client.newTxn().query(q);
    return response.data;
  }

  async mutate(mutation) {
    const txn = this.client.newTxn();

    try {
      if ((typeof mutation !== 'object') && mutation.includes("upsert")) {
        const response = await txn.mutate({ mutation: mutation, commitNow: true });
        return response;
      }
      if (typeof mutation == 'object') {
        const response = await txn.mutate({ setJson: mutation, commitNow: true });
        return response;
      }
      const response = await txn.mutate({ setNquads: mutation });
      await txn.commit();
      return response;
    } catch (e) {
      console.error('Error: ', e);
    } finally {
      await txn.discard();
    }
  }
  
}

export default new DgraphService();

I was wondering when the types didn’t match up if I may have the other js client than what you were using. I can’t reason to change out the client just for this one feature that I can work around. I already changed out the code to just use the ORM-like OOP upsert methods. And it works, so I’ll just leave it as it sits for now.

Thank you again though!