DEV Community

Jonathan Gamble
Jonathan Gamble

Posted on • Edited on

5 3

DGraph Advanced Data Modeling: Part 3 - Deep Updates

So I finished the coding for the Deep Updates. I have been out of town, so it took me a while. I had to re-write the code several times, and re-think it. There is a lot more to it than meets the eye.

That being said, I added two different versions:

  1. Remove and Set -- add-update / remove items, not just links. This basically works like a merge in noSQL.
  2. Arrays -- Overwrite entire array... delete all items, add new ones

I had to re-write the cascadeDelete from the previous post, adding _add and _delete to run the function from the deepUpdate function.

async function cascadeDelete({ 
  event, 
  dql, 
  nodes, 
  _delete = false, 
  _add = false
}) {

    const _nodes: string[] = nodes;

    const op = event.operation;

    if (op === 'delete' || op === 'add' || _delete || _add) {

        const uid = event[event.operation].rootUIDs[0];
        const invType = (event.__typename as string).toLowerCase();
        const type: string = event.__typename;

        const titleCase = (t: string) =>
            t.charAt(0).toUpperCase() + t.substring(1).toLowerCase();

        let args: any;

        if (op === 'delete' || _delete) {

            // get inverse relationships, delete them
            args = `upsert { query { `;
            for (let i = 0; i < _nodes.length; ++i) {
                const child = titleCase(_nodes[i]);
                // get all child.parent
                args += `t${i} as var(func: type(${child})) 
                @filter(uid_in(${child}.${invType}, ${uid})) `;
                // get all parent.child
                args += `q${i}(func: uid(${uid})) { b${i} as 
                ${titleCase(type)}.${_nodes[i].toLowerCase()} } `;
            }
            args += `} mutation { delete { `;
            for (let i = 0; i < _nodes.length; ++i) {
                // delete all child.parent
                args += `uid(t${i}) * * . \n`;
                // delete all parent.child
                args += `<${uid}> 
                <${titleCase(type)}.${_nodes[i].toLowerCase()}> 
                uid(b${i}) . `;
            }
            args += `} } }`;

        } else if (op === 'add' || _add) {

            // creates inverse relationships
            args = `upsert { query { q(func: uid(${uid})) { `;
            for (let i = 0; i < _nodes.length; ++i) {
                // get all 
                args += `t${i} as 
                ${type}.${_nodes[i].toLowerCase()} `;
            }
            args += `} } mutation { set { `;
            for (let i = 0; i < _nodes.length; ++i) {
                args += `uid(t${i}) 
                <${titleCase(_nodes[i])}.${invType}> <${uid}> . `
            }
            args += `} } }`;
        }
        console.log(args);
        const r = await dql.mutate(args);
        console.log(r);
    }
}
Enter fullscreen mode Exit fullscreen mode

And of course, you call the functions the same way:

async function featurePostHook({ event, dql }) {

    // update timestamps
    await updateTimestamps({ event, dql });

    // cascade delete
    await cascadeDelete({ event, dql, nodes: ['private'] });

    // deep update
    await deepUpdate({ event, dql, nodes: ['private'], merge: false });

}
Enter fullscreen mode Exit fullscreen mode

Add merge: false here if you want a deep array, or leave it the default true if you just want to update the values manually.

NOTE: Right now I only support ID types for deleting (remove). I may add the code later for @id types, but you can see below in the code where you would add it. I hate writing backend code, so I got burnt out trying to get everything going. If you use an array type (merge = false), this won't matter.

My goal here is to show people how things can be done, and help out the DGraph community.

async function deepUpdate({ event, dql, nodes, merge = true }) {

    const op = event.operation;

    if (op === 'update') {

        const uid = event[event.operation].rootUIDs[0];
        const removePatch: any = event.update.removePatch;
        const setPatch: any = event.update.setPatch;
        const type: string = event.__typename;

        // get updated keys
        let toRemove: string[];
        let toAdd: string[];

        if (removePatch) {
            toRemove = nodes.filter((v: string) => Object.keys(removePatch).includes(v));
        }
        if (setPatch) {
            toAdd = nodes.filter((v: string) => Object.keys(setPatch).includes(v));
        }

        const titleCase = (t: string) =>
            t.charAt(0).toUpperCase() + t.substring(1).toLowerCase();
        let args: any;

        if (merge) {

            if (setPatch) {
                // add inverse relationship
                await cascadeDelete({ dql, nodes, event, _add: true });
            }
            // remove objects in 'remove'
            if (toRemove) {

                // todo - upsert for xids or uids

                // get inverse relationships, delete them
                args = `{ delete { `;
                for (let i = 0; i < toRemove.length; ++i) {
                    // key input array
                    const patch: any[] = removePatch[toRemove[i]];
                    const ids = patch.map(v => Object.values(v)[0]);
                    // delete all child.parent
                    for (let j = 0; j < ids.length; ++j) {
                        args += `<${ids[j]}> * * . \n`;
                    }
                }
                args += `} }`;
            }
        } else {

            // delete all records in 'array'
            await cascadeDelete({ dql, nodes, event, _delete: true });

            // re-add everything
            for (let i = 0; i < toAdd.length; ++i) {
                args = `{ set { `;
                const child = setPatch[toAdd[i]];
                for (let j = 0; j < child.length; ++j) {
                    args += `<${uid}> <${type}.${toAdd[i].toLowerCase()}> _:new${j} . 
                    _:new${j} <${titleCase(toAdd[i])}.${type.toLowerCase()}> <${uid}> . 
                    _:new${j} <${titleCase(toAdd[i])}.${Object.keys(child[j])[0]}> "${Object.values(child[j])[0]}" . 
                    _:new${j} <dgraph.type> "${titleCase(toAdd[i])}" . \n`;
                }
                args += `} }`;
            }
        }
        // mutate
        console.log(args);
        const r = await dql.mutate(args);
        console.log(r);
    }
}
Enter fullscreen mode Exit fullscreen mode

And that's it! You can see where you would add the @id code.

Just add your code in an update mutation like so:

mutation {
    updatePost(input: { 
      filter: { id: "0x" },
      set: { 
        name: "bob", 
        url: "https:///something here", 
        nested: [{ text: "summer5" }, { text: "summer4" }] } 
}) {
        post {
            ....
            nested {
              text
            }
        }
        numUids
    }
}
Enter fullscreen mode Exit fullscreen mode

Or you can just set and remove what you want as usual. Remember, the remove must be ID types:

remove { postID: '0x2' }

Hope this helps!

Next up... counting likes, bookmarks, votes, etc!

J

Heroku

Build apps, not infrastructure.

Dealing with servers, hardware, and infrastructure can take up your valuable time. Discover the benefits of Heroku, the PaaS of choice for developers since 2007.

Visit Site

Top comments (3)

Collapse
 
lveillard profile image
lveillard

just created an account to say that if somebody has the issue:
"Invalid end of input"

just remove the extra line here:
args += uid(t${i})
<${titleCase(_nodes[i])}.${invType}> <${uid}> .

to fix it :)

Collapse
 
jdgamble555 profile image
Jonathan Gamble

Actually, just add a ‘\n’ to fix that. DQL requires newlines in some cases to process, unlike graphql.

Collapse
 
jurijurka profile image
Juri Jurka

thank you very much!!!! you are a gold boi for that awesome help and support!!!

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs