opencypher / morpheus

Morpheus brings the leading graph query language, Cypher, onto the leading distributed processing platform, Spark.
Apache License 2.0
337 stars 62 forks source link

Is there any workaround to run cypher "path" #959

Open ministat opened 1 year ago

ministat commented 1 year ago

Morpheus does not support "path" (https://github.com/opencypher/morpheus/blob/master/documentation/asciidoc/cypher-cypher9-features.adoc), but I encountered a problem which requires "path", I'd like to know is there any workaround.

MATCH path = (start:Application {alias: "r1shsummaryv2"})-[:DependsOn*]->(end:Application {alias: "r1appmetasvccont"})
UNWIND nodes(path) AS node
RETURN node {.alias, .properties} AS valueMap
Mats-SX commented 1 year ago

Hello @ministat

It was a long time ago since I (or anyone, really) worked on this library, so I do not recall exactly if we implemented support for variable-length relationship variables. If we did, you could try:

MATCH (start:Application {alias: "r1shsummaryv2"})-[r:DependsOn*]->(end:Application {alias: "r1appmetasvccont"})
UNWIND r AS relationship
WITH startNode(relationship) AS source
RETURN source {.alias, .properties} AS valueMap
ministat commented 1 year ago

@Mats-SX Thanks for your reply. Unfortunately, I got exception when I run the cyper you suggested. It looks like the pattern is unsupported.

Exception in thread "main" org.opencypher.okapi.impl.exception.NotImplementedException: Support for pattern conversion of RelationshipChain(NodePattern(Some(Variable(start)),ArrayBuffer(LabelName(Application)),None,None),RelationshipPattern(Some(Variable(r)),List(RelTypeName(DependsOn)),Some(None),None,OUTGOING,false,None),NodePattern(Some(Variable(end)),ArrayBuffer(LabelName(Application)),None,None)) not yet implemented
    at org.opencypher.okapi.ir.impl.PatternConverter$$anonfun$org$opencypher$okapi$ir$impl$PatternConverter$$convertElement$2$$anonfun$apply$3$$anonfun$apply$4$$anonfun$apply$5.apply(PatternConverter.scala:175)
    at org.opencypher.okapi.ir.impl.PatternConverter$$anonfun$org$opencypher$okapi$ir$impl$PatternConverter$$convertElement$2$$anonfun$apply$3$$anonfun$apply$4$$anonfun$apply$5.apply(PatternConverter.scala:127)
    at cats.data.StateFunctions$$anonfun$modify$4.apply(IndexedStateT.scala:299)
    at cats.data.StateFunctions$$anonfun$modify$4.apply(IndexedStateT.scala:299)
    at cats.data.StateFunctions$$anonfun$apply$19.apply(IndexedStateT.scala:289)
    at cats.data.StateFunctions$$anonfun$apply$19.apply(IndexedStateT.scala:289)
    at scala.Function1$$anonfun$andThen$1.apply(Function1.scala:52)
    at cats.data.IndexedStateT$$anonfun$run$1.apply(IndexedStateT.scala:66)
    at cats.data.IndexedStateT$$anonfun$run$1.apply(IndexedStateT.scala:66)
    at cats.Eval$.loop$1(Eval.scala:351)
    at cats.Eval$.cats$Eval$$evaluate(Eval.scala:372)
    at cats.Eval$FlatMap.value(Eval.scala:308)
    at org.opencypher.okapi.ir.impl.PatternConverter.convert(PatternConverter.scala:58)
    at org.opencypher.okapi.ir.impl.IRBuilderContext.convertPattern(IRBuilderContext.scala:64)
    at org.opencypher.okapi.ir.impl.IRBuilder$$anonfun$org$opencypher$okapi$ir$impl$IRBuilder$$convertPattern$1.apply(IRBuilder.scala:546)
    at org.opencypher.okapi.ir.impl.IRBuilder$$anonfun$org$opencypher$okapi$ir$impl$IRBuilder$$convertPattern$1.apply(IRBuilder.scala:544)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.EffInterpretation$$anonfun$runEval$1$1.apply(Eff.scala:361)
    at org.atnos.eff.EffInterpretation$$anonfun$runEval$1$1.apply(Eff.scala:361)
    at cats.Later.value$lzycompute(Eval.scala:151)
    at cats.Later.value(Eval.scala:150)
    at cats.Eval$.loop$1(Eval.scala:351)
    at cats.Eval$.cats$Eval$$evaluate(Eval.scala:372)
    at cats.Eval$FlatMap.value(Eval.scala:308)
    at org.atnos.eff.EffInterpretation$class.run(Eff.scala:369)
    at org.atnos.eff.Eff$.run(Eff.scala:149)
    at org.atnos.eff.syntax.EffNoEffectOps$.run$extension(eff.scala:59)
    at org.opencypher.okapi.ir.impl.package$RichIRBuilderStack.run(package.scala:52)
    at org.opencypher.okapi.ir.impl.IRBuilder$.process(IRBuilder.scala:56)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession$$anonfun$6.apply(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession$$anonfun$6.apply(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession.time(RelationalCypherSession.scala:119)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession.cypherOnGraph(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherGraph$class.cypher(RelationalCypherGraph.scala:106)
    at org.opencypher.okapi.relational.impl.graph.ScanGraph.cypher(ScanGraph.scala:43)
    at org.opencypher.okapi.relational.impl.graph.ScanGraph.cypher(ScanGraph.scala:43)
Mats-SX commented 1 year ago

As a last attempt, try adding an upper bound to the variable-length pattern. I noted that we declared unbounded var-length patterns as not implemented here.

MATCH (start:Application {alias: "r1shsummaryv2"})-[r:DependsOn*..100]->(end:Application {alias: "r1appmetasvccont"})
UNWIND r AS relationship
WITH startNode(relationship) AS source
RETURN source {.alias, .properties} AS valueMap
ministat commented 1 year ago

The exception changed, but unfortunately, it still failed for something not implemented.

23/09/27 10:25:12 WARN SparkSession$Builder: Using an existing SparkSession; some configuration may not take effect.
Exception in thread "main" org.opencypher.okapi.impl.exception.NotImplementedException: The expression DesugaredMapProjection(Variable(source),List(LiteralEntry(PropertyKeyName(alias),Property(Variable(source),PropertyKeyName(alias))), LiteralEntry(PropertyKeyName(properties),Property(Variable(source),PropertyKeyName(properties)))),false) [line 4, column 8 (offset: 189)] is not supported by the system
    at org.opencypher.okapi.ir.impl.IRBuilderContext.infer(IRBuilderContext.scala:84)
    at org.opencypher.okapi.ir.impl.IRBuilderContext.convertExpression(IRBuilderContext.scala:68)
    at org.opencypher.okapi.ir.impl.IRBuilder$$anonfun$org$opencypher$okapi$ir$impl$IRBuilder$$convertExpr$2.apply(IRBuilder.scala:567)
    at org.opencypher.okapi.ir.impl.IRBuilder$$anonfun$org$opencypher$okapi$ir$impl$IRBuilder$$convertExpr$2.apply(IRBuilder.scala:566)
    at org.atnos.eff.Continuation$$anonfun$map$1.apply(Continuation.scala:39)
    at org.atnos.eff.Continuation$$anonfun$map$1.apply(Continuation.scala:39)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Interpret$$anonfun$interpretContinuation$1$1.apply(Interpret.scala:43)
    at org.atnos.eff.Continuation.go$1(Continuation.scala:54)
    at org.atnos.eff.Continuation.apply(Continuation.scala:72)
    at org.atnos.eff.EffInterpretation$$anonfun$runEval$1$1.apply(Eff.scala:361)
    at org.atnos.eff.EffInterpretation$$anonfun$runEval$1$1.apply(Eff.scala:361)
    at cats.Later.value$lzycompute(Eval.scala:151)
    at cats.Later.value(Eval.scala:150)
    at cats.Eval$.loop$1(Eval.scala:351)
    at cats.Eval$.cats$Eval$$evaluate(Eval.scala:372)
    at cats.Eval$FlatMap.value(Eval.scala:308)
    at org.atnos.eff.EffInterpretation$class.run(Eff.scala:369)
    at org.atnos.eff.Eff$.run(Eff.scala:149)
    at org.atnos.eff.syntax.EffNoEffectOps$.run$extension(eff.scala:59)
    at org.opencypher.okapi.ir.impl.package$RichIRBuilderStack.run(package.scala:52)
    at org.opencypher.okapi.ir.impl.IRBuilder$.process(IRBuilder.scala:56)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession$$anonfun$6.apply(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession$$anonfun$6.apply(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession.time(RelationalCypherSession.scala:119)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherSession.cypherOnGraph(RelationalCypherSession.scala:169)
    at org.opencypher.okapi.relational.api.graph.RelationalCypherGraph$class.cypher(RelationalCypherGraph.scala:106)
    at org.opencypher.okapi.relational.impl.graph.ScanGraph.cypher(ScanGraph.scala:43)
    at org.opencypher.okapi.relational.impl.graph.ScanGraph.cypher(ScanGraph.scala:43)
    at com.ebay.nugraph.gremlintomorpheus.Sherlock$.executeCypher(Sherlock.scala:260)
    at com.ebay.nugraph.gremlintomorpheus.MainEntry$.main(MainEntry.scala:16)
    at com.ebay.nugraph.gremlintomorpheus.MainEntry.main(MainEntry.scala)
ministat commented 1 year ago

@Mats-SX Do you have any document introducing the design and implementation? I'd like to evaluate the effort to support this feature. That will be great if you or your colleagues can provide some help and guide.

DarthMax commented 1 year ago

I think you are quite close, this should work:

MATCH (start:Application {alias: "r1shsummaryv2"})-[r:DependsOn*..100]->(end:Application {alias: "r1appmetasvccont"})
UNWIND r AS relationship
WITH startNode(relationship) AS source
RETURN source.alias, source.properties

There is one caveat though. IIRC the Variable Path Expand will always try to do as many expands as you specify, because we basically unroll this into optional joins in spark. So you might want to play around with that upper bound to get faster query results

ministat commented 1 year ago

Yes. It looks quite close to be working. However, there is an exception. Can I ignore this?

Exception in thread "main" org.opencypher.okapi.impl.exception.IllegalArgumentException: 

Expected:
    Header does not contain a column for r :: LIST(RELATIONSHIP(:DependsOn) @ session.tmp1).
    [`end._label_`, `end.alias`, `end.label`, `end.lcmstate`, `end.name`, `end.type`, `end:Application`, `end`, `explode(r)`, `r(1).edgelabel`, `r(1):DependsOn`, `r(1)`, `r(2).edgelabel`, `r(2):DependsOn`, `r(2)`, `relationship`, `source(r(1))`, `source(r(2))`, `start._label_`, `start.alias`, `start.label`, `start.lcmstate`, `start.name`, `start.type`, `start:Application`, `start`, `target(r(1))`, `target(r(2))`]
Found:
    none
    at org.opencypher.okapi.relational.impl.table.RecordHeader$$anonfun$column$1.apply(RecordHeader.scala:88)
    at org.opencypher.okapi.relational.impl.table.RecordHeader$$anonfun$column$1.apply(RecordHeader.scala:88)
    at scala.collection.MapLike$class.getOrElse(MapLike.scala:128)
    at scala.collection.AbstractMap.getOrElse(Map.scala:59)
    at org.opencypher.okapi.relational.impl.table.RecordHeader.column(RecordHeader.scala:88)
    at org.opencypher.spark.impl.SparkSQLExprMapper$RichExpression.asSparkSQLExpr(SparkSQLExprMapper.scala:133)
    at org.opencypher.spark.impl.SparkSQLExprMapper$RichExpression.asSparkSQLExpr(SparkSQLExprMapper.scala:371)
    at org.opencypher.spark.impl.SparkSQLExprMapper$RichExpression.asSparkSQLExpr(SparkSQLExprMapper.scala:141)
    at org.opencypher.spark.impl.table.SparkTable$DataFrameTable$$anonfun$4.apply(SparkTable.scala:85)
    at org.opencypher.spark.impl.table.SparkTable$DataFrameTable$$anonfun$4.apply(SparkTable.scala:84)
    at scala.collection.LinearSeqOptimized$class.foldLeft(LinearSeqOptimized.scala:124)
    at scala.collection.immutable.List.foldLeft(List.scala:84)
    at org.opencypher.spark.impl.table.SparkTable$DataFrameTable.withColumns(SparkTable.scala:84)
    at org.opencypher.spark.impl.table.SparkTable$DataFrameTable.withColumns(SparkTable.scala:53)
    at org.opencypher.okapi.relational.impl.operators.Add._table$lzycompute(RelationalOperator.scala:246)
    at org.opencypher.okapi.relational.impl.operators.Add._table(RelationalOperator.scala:240)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.impl.operators.Add._table$lzycompute(RelationalOperator.scala:246)
    at org.opencypher.okapi.relational.impl.operators.Add._table(RelationalOperator.scala:240)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.impl.operators.Add._table$lzycompute(RelationalOperator.scala:246)
    at org.opencypher.okapi.relational.impl.operators.Add._table(RelationalOperator.scala:240)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.impl.operators.Add._table$lzycompute(RelationalOperator.scala:246)
    at org.opencypher.okapi.relational.impl.operators.Add._table(RelationalOperator.scala:240)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.impl.operators.Select._table$lzycompute(RelationalOperator.scala:330)
    at org.opencypher.okapi.relational.impl.operators.Select._table(RelationalOperator.scala:328)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.impl.operators.AlignColumnsWithReturnItems._table$lzycompute(RelationalOperator.scala:355)
    at org.opencypher.okapi.relational.impl.operators.AlignColumnsWithReturnItems._table(RelationalOperator.scala:353)
    at org.opencypher.okapi.relational.impl.operators.RelationalOperator.table(RelationalOperator.scala:77)
    at org.opencypher.okapi.relational.api.planning.RelationalCypherResult$$anonfun$getRecords$1.apply(RelationalCypherResult.scala:70)
    at org.opencypher.okapi.relational.api.planning.RelationalCypherResult$$anonfun$getRecords$1.apply(RelationalCypherResult.scala:64)
    at scala.Option.flatMap(Option.scala:171)
    at org.opencypher.okapi.relational.api.planning.RelationalCypherResult.getRecords(RelationalCypherResult.scala:64)
    at org.opencypher.okapi.api.graph.CypherResult$class.records(CypherResult.scala:71)
    at org.opencypher.okapi.relational.api.planning.RelationalCypherResult.records(RelationalCypherResult.scala:38)
    at com.ebay.nugraph.gremlintomorpheus.Sherlock$.executeCypher(Sherlock.scala:270)
    at com.ebay.nugraph.gremlintomorpheus.MainEntry$.main(MainEntry.scala:16)
    at com.ebay.nugraph.gremlintomorpheus.MainEntry.main(MainEntry.scala)

Source code in SparkSQLExprMapper.scala:

        case _: Var | _: Param | _: HasLabel | _: HasType | _: StartNode | _: EndNode =>
          verify

          val colName = header.column(expr) // <- throw exception
          if (df.columns.contains(colName)) {
            df.col(colName)
          } else {
            NULL_LIT
          }
Mats-SX commented 1 year ago

I really don't recall how this works. It looks like we're expanding the relationship variable r into multiple variables r(x) where x denotes the distance from the source node. So with an upper bound of 10 you would get 10 such r variables. But we're not renaming things well enough, because explode(r) references just r not r(x) which cannot be found.

I couldn't say what is wrong -- my best advice is to try some more alternatives, maybe this one:

MATCH (start:Application {alias: "r1shsummaryv2"})-[r:DependsOn*..2]->(end:Application {alias: "r1appmetasvccont"})
RETURN r

But it could also be the case that what you're trying to do is not properly implemented. In case you want to implement more support feel free to go ahead. At Neo4j we are not pursuing anything with this project currently. We do have another project https://github.com/neo4j-contrib/neo4j-spark-connector which is actively maintained. I've contacted the people who work on that to see if it can help your use case.

ministat commented 1 year ago

Thanks for your patient response. The following cypher does not throw exception nor give the expected result.

MATCH (start:Application {alias: "r1shsummaryv2"})-[r:DependsOn*..2]->(end:Application {alias: "r1appmetasvccont"})
RETURN r
╔══════╤══════╤══════════════════════════════════════════════════════════════════════════════════════════╗
║ r(1) │ r(2) │ r                                                                                        ║
╠══════╪══════╪══════════════════════════════════════════════════════════════════════════════════════════╣
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}]]]                                            ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
║ null │ null │ [[[:`DependsOn` {`edgelabel`: 'DependsOn'}], [:`DependsOn` {`edgelabel`: 'DependsOn'}]]] ║
╚══════╧══════╧══════════════════════════════════════════════════════════════════════════════════════════╝