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:
- Remove and Set -- add-update / remove items, not just links. This basically works like a merge in noSQL.
- 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); } }
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 }); }
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); } }
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 } }
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
Top comments (3)
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 :)
Actually, just add a ‘\n’ to fix that. DQL requires newlines in some cases to process, unlike graphql.
thank you very much!!!! you are a gold boi for that awesome help and support!!!