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