Skip to content

Instantly share code, notes, and snippets.

@lindacmsheard
Last active January 10, 2022 23:33
Show Gist options
  • Save lindacmsheard/c0273e272045911e9745dd52b864c12b to your computer and use it in GitHub Desktop.
Save lindacmsheard/c0273e272045911e9745dd52b864c12b to your computer and use it in GitHub Desktop.
Gremlin: Upsert an Edge

Context: this question

The following commands can be exectuted from within the Data Explorer interface of an Azure Cosmos DB Gremlin API database resource, or from other gremlin consoles such as a local TinkerPop installation.

Create required vertices

This can be in an empty graph, like that created when adding a graph in Azure Cosmos DB Gremlin API or added to an exsisting graph, like for example the 'modern' sample graph from the Tinkerpop Getting Started documentation.

g.addV("person").property("pk","pk").property("id","a").property("name","Alice")
g.addV("person").property("pk","pk").property("id","c").property("name","Charlie")

💡 Note that in the following, we show selecting a Vertex by property (V().has("property","value")). This assumes that the Vertex ID is not known by the application. In the local Tinkerpop Gremlin console, the ID is an autogenerated integer, and so this approach is compatible. In Cosmos DB Gremlin API, the ID is the id property, which can be set as a string as in the example above. This could simplify node selection to V('<id>').

Upsert Edge

View existing edges of type reports_to:

g.V().has("name","Charlie").outE("reports_to").where(inV().has("name","Alice"))

💡 Note, Cosmos DB adds a label property to edges, meaning the same filtering can be achieved by using a generic outE() in the statement, and adding a property filter as below. This does not work in a local TinkerPop console.

g.V().has("name","Charlie").outE().where(inV().has("name","Alice")).
      has("label","reports_to")
-> none

Add a new edge if it does not exist (Upsert)

g.V().has("name","Charlie").outE("reports_to").where(inV().has("name","Alice")).
      fold().
      coalesce(
        unfold(),
        __.addE("reports_to").from(__.V().has("name","Charlie")).to(__.V().has("name","Alice"))).
      property("duration","1y")
[
  {
    "id": "44011121-037c-4900-b857-cfbb1dc6c23a",
    "label": "reports_to",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "1y"
    }
  }
]

Update the same edge (Upsert)

g.V().has("name","Charlie").outE("reports_to").where(inV().has("name","Alice")).
      fold().
      coalesce(
        unfold(),
        __.addE("reports_to").from(__.V().has("name","Charlie")).to(__.V().has("name","Alice"))).
      property("duration","4y")
[
  {
    "id": "44011121-037c-4900-b857-cfbb1dc6c23a",
    "label": "reports_to",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "4y"
    }
  }
]

Remove the relationship type constraint: view relationships:

g.V().has("name","Charlie").outE().where(inV().has("name","Alice"))

This may return multiple relationships between the two nodes, if the manages relationship was created previously:

[
  {
    "id": "0d6531cf-ec76-4be3-b568-b1fc0b758ab2",
    "label": "manages",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "2y"
    }
  },
  {
    "id": "44011121-037c-4900-b857-cfbb1dc6c23a",
    "label": "reports_to",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "4y"
    }
  }
]

⚠️ The following command would now update both relationships, so instead we have to ensure the initial fold is filtered down to the specific relationship that should be updated, as in the first example above.

g.V().has("name","Charlie").outE().where(inV().has("name","Alice")).
      fold().
      coalesce(
        unfold(),
        __.addE("manages").from(__.V().has("name","Charlie")).to(__.V().has("name","Alice"))).
      property("duration","3y")
[
  {
    "id": "0d6531cf-ec76-4be3-b568-b1fc0b758ab2",
    "label": "manages",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "3y"
    }
  },
  {
    "id": "44011121-037c-4900-b857-cfbb1dc6c23a",
    "label": "reports_to",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "3y"
    }
  }
]

⚠️ We also can't create a new relationship (follows) and update the property in one go, as the other relationships would trigger the unfold rather than the new relationship creation within the coalesce statement. Again, the existing relationships are updated and the addE('follows') is never hit.

g.V().has("name","Charlie").outE().where(inV().has("name","Alice")).
      fold().
      coalesce(
        unfold(),
        __.addE("follows").from(__.V().has("name","Charlie")).to(__.V().has("name","Alice"))).
      property("duration","6y")
[
  {
    "id": "0d6531cf-ec76-4be3-b568-b1fc0b758ab2",
    "label": "manages",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "6y"
    }
  },
  {
    "id": "44011121-037c-4900-b857-cfbb1dc6c23a",
    "label": "reports_to",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "6y"
    }
  }
]

✔️ instead, to add a new 'follows' relationship, ensure the initial fold is filtered to that specific relationship, as per the Upsert example at the start of this post:

g.V().has("name","Charlie").outE("follows").where(inV().has("name","Alice")).
      fold().
      coalesce(
        unfold(),
        __.addE("follows").from(__.V().has("name","Charlie")).to(__.V().has("name","Alice"))).
      property("duration","6y")
[
  {
    "id": "d168a86c-6d9e-4373-a4e6-98211fb80958",
    "label": "follows",
    "type": "edge",
    "inVLabel": "person",
    "outVLabel": "person",
    "inV": "a",
    "outV": "c",
    "properties": {
      "duration": "6y"
    }
  }
]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment