Arne Böckmann
Arne Böckmann

Reputation: 487

Gremlin Python - Add unknown number of properties to edge on edge creation if edge does not exist

The query should to the following:

The following works if traversal is a pristine traversal. If traversal already contains some other steps (e.g. creation of a vertex) it crashes with below error.


properties = {"p1": "p1_value", "p2": "p2_value"}

traversal.V().inject(properties).as_(props_label).
V().has("uuid",from_uuid).as_(from_label).
V().has("uuid",to_uuid).as_(to_label).
coalesce(inE(edge_label).where(outV().
as_(from_label)),addE(edge_label).
from_(from_label).as_(e_label).select(props_label).
unfold().as_(kv_label).select(e_label).
property(select(kv_label).by(Column.keys),select(kv_label).by(Column.values))).iterate()
gremlin_python.driver.protocol.GremlinServerError: 500: The provided object does not have accessible keys: class org.janusgraph.graphdb.vertices.StandardVertex

If I iterate() the traversal before injecting, it works. But I would like to avoid the iterate for performance reasons.

Any ideas?

edit:

I did some more testing. Adding the vertices beforehand works. Adding them in the same query does not.

This works:

gremlin> g.addV("TestType").property("name", "1")
==>v[2302192]
gremlin> g.addV("TestType").property("name", "2")
==>v[2326704]
gremlin> g.inject(["p1": "v1", "p2": "v2"]).unfold().as("props").V(2302192).as("from").V(2326704).as("to").coalesce(inE("DEPENDS_ON").where(outV().as("from")), addE("DEPENDS_ON").from("from").property(select("props").by(keys), select("props").by(values)))
==>e[187by-1dcds-1lh-1dvao][2302192-DEPENDS_ON->2326704]
==>e[187by-1dcds-1lh-1dvao][2302192-DEPENDS_ON->2326704]

This fails:

gremlin> g.addV("TestType").property("name", "1").addV("TestType").property("name", "2").inject(["p1": "v1", "p2": "v2"]).unfold().as("props").V().has("name", "1").as("from").V().has("name", "2").as("to").coalesce(inE("DEPENDS_ON").where(outV().as("from")), addE("DEPENDS_ON").from("from").property(select("props").by(keys), select("props").by(values))).dedup()
The provided object does not have accessible keys: class org.janusgraph.graphdb.vertices.StandardVertex

Upvotes: 1

Views: 1263

Answers (2)

Arne Böckmann
Arne Böckmann

Reputation: 487

Just for reference for anybody who might stumble upon this in the future. I found a solution that is much simpler using an anonymous traversal instead of injection:

properties = {"some key": "some value", "etc": "bla"}

# prepare the add vertex sub traversal
add_vertex_traversal = addV(label)
for k, v in properties.items():
    add_vertex_traversal = add_vertex_traversal.property(k, v)

g.V().has('some_prop', "some_value").fold().coalesce(unfold(),add_vertex_traversal)) 

Upvotes: 2

stephen mallette
stephen mallette

Reputation: 46226

As an initial point, you do not need the first V in your traversal. You just need to start with inject(). If you start with V() you end up executing the following steps for every vertex in the graph.

That said, I don't see a problem with your traversal and it works fine with TinkerGraph once I adapted it to the modern toy graph:

gremlin> g = TinkerFactory.createModern().traversal()
==>graphtraversalsource[tinkergraph[vertices:6 edges:6], standard]
gremlin> properties = [p1: "p1_value", p2: "p2_value"]
==>p1=p1_value
==>p2=p2_value
gremlin> g.inject(properties).as('props_label').
......1>   V().has("name",'marko').as('from_label').
......2>   V().has("name",'josh').as('to_label').
......3>   coalesce(inE('knows').where(outV().as('from_label')),
......4>            addE('knows').from('from_label').as('e_label').select('props_label').
......5>              unfold().as('kv_label').select('e_label').
......6>              property(select('kv_label').by(Column.keys),
......7>                       select('kv_label').by(Column.values)))
==>e[8][1-knows->4]
gremlin> g.inject(properties).as('props_label').
......1>   V().has("name",'peter').as('from_label').
......2>   V().has("name",'vadas').as('to_label').
......3>   coalesce(inE('knows').where(outV().as('from_label')),
......4>            addE('knows').from('from_label').as('e_label').select('props_label').
......5>              unfold().as('kv_label').select('e_label').
......6>              property(select('kv_label').by(Column.keys),
......7>                       select('kv_label').by(Column.values)))
==>e[13][6-knows->2]
==>e[13][6-knows->2]

You may wish to dedup() results on this traversal as you will get one result for each property key in the map given its unfold() at line 5.

The error you're getting is a server-side error and I don't think an issue with gremlinpython. It points to a situation where select('kv_label').by(Column.keys) is trying to access a JanusGraph StandardVertex object. I can recreate that problem in TinkerGraph easily enough given your updated question:

gremlin> g.addV("TestType").property("name", "1").
......1>   addV("TestType").property("name", "2").
......2>   inject(["p1": "v1", "p2": "v2"]).
......3>   unfold().as("props").
......4>   V().has("name", "1").as("from").
......5>   V().has("name", "2").as("to").
......6>   coalesce(inE("DEPENDS_ON").where(outV().as("from")), 
......7>            addE("DEPENDS_ON").from("from").
......8>              property(select("props").by(keys), select("props").by(values))).dedup()
The provided object does not have accessible keys: class org.apache.tinkerpop.gremlin.tinkergraph.structure.TinkerVertex
Type ':help' or ':h' for help.
Display stack trace? [yN]n

This is a very different traversal from your original question which is why I wasn't able to recreate it. You're expecting just key/value pairs from your inject() in the stream but it in fact has something else:

gremlin> g.addV("TestType").property("name", "1").
......1>   addV("TestType").property("name", "2").
......2>   inject(["p1": "v1", "p2": "v2"]).
......3>   unfold()
==>p1=v1
==>p2=v2
==>v[2]

When v[2] makes its way to select("props").by(keys) you get the exception I described. You can fix it by moving inject() to the start or using withSideEffect()

gremlin> g.inject(["p1": "v1", "p2": "v2"]).as('props').
......1>   addV("TestType").property("name", "1").
......2>   addV("TestType").property("name", "2").
......3>   V().has("name", "1").as("from").
......4>   V().has("name", "2").as("to").
......5>   coalesce(inE("DEPENDS_ON").where(outV().as("from")), 
......6>            addE("DEPENDS_ON").from("from").as('e').
......7>              select('props').
......8>              unfold().as('kv').
......9>              select('e').
.....10>              property(select("kv").by(keys), select("kv").by(values))).dedup()
==>e[4][0-DEPENDS_ON->2]
gremlin> g.withSideEffect('props', ["p1": "v1", "p2": "v2"]).
......1>   addV("TestType").property("name", "1").
......2>   addV("TestType").property("name", "2").
......3>   V().has("name", "1").as("from").
......4>   V().has("name", "2").as("to").
......5>   coalesce(inE("DEPENDS_ON").where(outV().as("from")), 
......6>            addE("DEPENDS_ON").from("from").as('e').
......7>              select('props').
......8>              unfold().as('kv').
......9>              select('e').
.....10>              property(select("kv").by(keys), select("kv").by(values))).dedup()
==>e[4][0-DEPENDS_ON->2]

I'm not sure which is more intuitive. I tend to prefer inject() when I need something to start the traversal, but you have addV() to put objects in the start, so inject() seems clumsy there especially since addV() just replaces the Map its given. In this case, I think using withSideEffect() more explicitly tells someone reading this what the intent is.

Upvotes: 2

Related Questions