我正在努力将图从旧的命名系统迁移到新的命名系统,并正在寻找一种有效的方法来有效地重新标记给定的节点标签,而不会丢失节点或边属性或任何边。我正在寻找创建一个函数,可以对具有任何边缘标签集的任何节点标签执行此操作。
我已经能够轻松地复制所有节点,并且其属性完好无损,但跨边缘迁移则更加痛苦。如果我拉出所有边并逐一重新添加它们,这很容易,但这种方法非常慢,因为它需要对每个边进行查询。
在尝试将这一切编写为一个查询时,我遇到了两个主要问题。首先是动态应用边缘标签似乎不可能。如果我尝试类似的操作,它似乎会传递一个遍历器而不是一个值:
await g.V().hasLabel('nodeLabel').outE().as('e1').addE(__.select('e1').label())
我想我可以通过抓取节点类型上的边缘列表并像这样动态构建查询来解决这个问题?:
query = query.outE().hasLabel(edgeLabels[i]).addE(edgeLabels[i]) etc
第二个更重要的问题是在一个查询中对所有边进行分配和分配。如果我不小心,有两件事可能会出错。首先,往返于我正在重新标记的节点的边将不会正确更新并最终指向旧节点。其次,往返于我要重新标记的节点的边可以创建两次(一次来自 inE() 步骤,一次在 outE() 步骤之后)。我考虑过在所有节点之后更新边缘(当我可以有从旧节点 id 到新节点 id 的映射时),但这似乎需要 lambda,这超出了我当前的经验。此外,lambda 的文档非常稀疏,我什至不清楚它是否会接受包含映射的闭包,所以如果可能的话,我想避免与它发生冲突。对于哈希映射来说,这一切相对来说都是微不足道的,但这很快让我进入了 Slowwww 领域,因为我目前正在处理它,因为它将我所需的查询数量分解为 O(E),其中 E 是顶点标签类。
作为参考,我目前正在迁移一个具有大约 30k 边的类,据我估计,这似乎需要大约 2 小时。
我希望有一种更快的方法来做到这一点。我对这有多困难感到惊讶,因为我认为这是一项人们以(稀疏)规律性进行的操作。
以下是我目前的工作,但速度很慢:
async migrateNodes(
selectToDuplicate: gremlin.process.GraphTraversal,
newLabel: string,
edgeLabelReplacements: Map<string, string> = new Map<string, string>(),
) {
const originalAndDuplicateIds = (await selectToDuplicate
.as('original')
.addV(newLabel)
.as('duplicate')
.sideEffect(
__.select('original')
.properties()
.unfold()
.as('props')
.select('duplicate')
.property(__.select('props').key(), __.select('props').value()),
)
.project('original', 'duplicate')
.by(__.select('original').id())
.by(__.select('duplicate').id())
.toList()) as Map<any, any>[];
type OriginalAndDuplicateIds = {
original: string;
duplicate: string;
};
const originalAndDuplicateIdsParsed = originalAndDuplicateIds.map((ele) =>
mapToObject<OriginalAndDuplicateIds>(ele),
);
await this.duplicateEdges(originalAndDuplicateIdsParsed, edgeLabelReplacements);
}
async duplicateEdges(
originalAndDuplicateIds: any[],
edgeLabelReplacements: Map<string, string> = new Map<string, string>(),
) {
// original and duplicate projection
let originalIds = [];
let duplicateIds = [];
let limit = pLimit(2000);
let tasks = [];
for (let i = 0; i < originalAndDuplicateIds.length; i++) {
originalIds.push(originalAndDuplicateIds[i].original);
duplicateIds.push(originalAndDuplicateIds[i].duplicate);
}
let seen = new Map<string, boolean>();
for (let i = 0; i < originalAndDuplicateIds.length; i++) {
const originalId = originalAndDuplicateIds[i].original;
const duplicateId = originalAndDuplicateIds[i].duplicate;
const outEdges = (await this.g.V(originalId).outE().toList()) as Edge[];
const inEdges = (await this.g.V(originalId).inE().toList()) as Edge[];
for (let edge of outEdges) {
let inVId = edge.inV.id;
if (originalIds.includes(inVId)) {
inVId = duplicateIds[originalIds.indexOf(inVId)];
}
if (seen.has(edge.id)) {
continue;
}
seen.set(edge.id, true);
let label = edge.label;
if (edgeLabelReplacements.has(label)) {
label = edgeLabelReplacements.get(label) as string;
}
tasks.push(
limit(() => {
this.edgeCounter++;
console.log('running task ' + this.edgeCounter);
return this.g
.E(edge.id)
.as('e1')
.V(duplicateId)
.as('out')
.V(inVId)
.as('in')
.addE(label)
.from_(__.select('out'))
.to(__.select('in'))
.as('e2')
.sideEffect(
__.select('e1')
.properties()
.unfold()
.as('props')
.select('e2')
.property(__.select('props').key(), __.select('props').value()),
)
.iterate();
}),
);
}
for (let edge of inEdges) {
let outVId = edge.outV.id;
if (originalIds.includes(outVId)) {
outVId = duplicateIds[originalIds.indexOf(outVId)];
}
if (seen.has(edge.id)) {
continue;
}
seen.set(edge.id, true);
let label = edge.label;
if (edgeLabelReplacements.has(label)) {
label = edgeLabelReplacements.get(label) as string;
}
tasks.push(
limit(() => {
this.edgeCounter++;
console.log('running task ' + this.edgeCounter);
return this.g
.E(edge.id)
.as('e1')
.V(outVId)
.as('out')
.V(duplicateId)
.as('in')
.addE(label)
.from_(__.select('out'))
.to(__.select('in'))
.as('e2')
.sideEffect(
__.select('e1')
.properties()
.unfold()
.as('props')
.select('e2')
.property(__.select('props').key(), __.select('props').value()),
)
.iterate();
}),
);
}
tasks.push(
limit(() => {
this.edgeCounter = 0;
return;
}),
);
}
await Promise.all(tasks);
}
不幸的是,Gremlin 使这变得困难,因为标签是不可变的,因此您无法删除现有标签。
如果您使用 Amazon Neptune,并且想要更改标签名称,您可以利用 openCypher 和 Gremlin 之间的互操作性,使用类似于以下的 openCypher 更改标签名称:
MATCH (n:airport {code: 'ANC'}) SET n:airport2 REMOVE n:airport RETURN n