Skip to content

Instantly share code, notes, and snippets.

@spmallette
Last active July 12, 2023 13:50
Show Gist options
  • Save spmallette/5cd448f38d5dae832c67d890b576df31 to your computer and use it in GitHub Desktop.
Save spmallette/5cd448f38d5dae832c67d890b576df31 to your computer and use it in GitHub Desktop.
Gremlin merge() API

Gremlin mergeV/E() API

mergeV/E(Map|Traversal search).
  option(Merge.onCreate, Map|Traversal).
  option(Merge.onMatch, Map|Traversal)

Examples

mergeV() - "get or create"

approximation of fold().coalesce(unfold(), addV())

// without any option(), the behavior is the same as above, creating using search criteria
// if there is no match
g.mergeV([(T.label): 'person', name: 'marko', age: 29])

// if matched the Vertex will be returned, otherwise create the Vertex with the specified Map
g.mergeV([(T.id): 1]).
    option(Merge.onCreate, [(T.label): 'person', name: 'marko', age: 29])

// if matched the Vertex will be returned, otherwise create the Vertex with the dynamically specified Map
g.withSideEffect('c', [(T.label): 'person', name: 'marko', age: 29]).
  mergeV([(T.id): 1]).
    option(Merge.onCreate, select('c'))

mergeV() - "get/update or create"

approximation of fold().coalesce(unfold().property(...), addV())

// if matched the Vertex will be returned with the "age" property updated, otherwise create the
// Vertex with the specified Map
g.mergeV([(T.id): 1]).
    option(Merge.onCreate, [(T.label): 'person', name: 'marko', age: 29]).
    option(Merge.onMatch, [age: 30])

// if matched the Vertex will be returned with "age" property from the dynamically specified Map of 
// "m", otherwise create the Vertex with the dynamically specified Map of "c"
g.withSideEffect('c', [(T.label): 'person', name: 'marko', age: 29]).
  withSideEffect('m', [age: 30]).
  mergeV([(T.id): 1]).
    option(Merge.onCreate, select('c'))
    option(Merge.onMatch, select('m'))
    
// if matched on the dynamically established search criteria of "s", the Vertex will be returned with 
// "age" property from the dynamically specified Map of "m", otherwise create the Vertex with the 
// dynamically specified Map of "c"
g.withSideEffect('s', [(T.id): 1]).
  withSideEffect('c', [(T.label): 'person', name: 'marko', age: 29]).
  withSideEffect('m', [age: 30]).
  mergeV(select('s')).
    option(Merge.onCreate, select('c'))
    option(Merge.onMatch, select('m'))    

mergeV() with Meta Properties

// match on id and return if found otherwise create a Vertex (attempting to set "2" for the id
// if the graph supports it) and in either case set the "name" property and its associated meta
// property
g.mergeV([(T.id): 2]).
  property('name','vadas','acl','public')

// or use the additional properties syntax
g.mergeV([(T.label): 'person', name:'vadas']).
  properties('name').property('acl','public')

// or if there were multi-properties then maybe...
g.mergeV([(T.label): 'person', name:'vadas']).
  properties('name').hasValue('vadas').property('acl','public')

mergeV() with Multi Properties

// this a List object
g.mergeV([(T.label): 'person', lang: ['java', 'scala', 'java'])

// this is a multi-property
g.mergeV([(T.id): 1, (T.label): 'person']).
  property(list, 'lang', 'java'). 
  property(list, 'lang', 'scala').
  property(list, 'lang', 'java')

mergeV() with stream

g.inject([(T.id): 1, (T.label): 'person', name: 'marko'],
         [(T.id): 2, (T.label): 'person', name: 'josh']).
  mergeV(identity())
g.inject([(T.id): 1, (T.label): 'person', name: 'marko'],
         [(T.id): 2, (T.label): 'person', name: 'josh']).
  mergeV()  
  
g.inject([[id: 1], [label: 'person', name: 'marko', created: 2020],[updated: 2021]],
         [[id: 2], [label: 'person', name: 'josh', created: 2020], [updated: 2021]).
  mergeV(limit(local,1)).
    option(onCreate, range(local, 1, 2)).
    option(onMatch, tail(local))

mergeV() explicit failure

// ensure that the a Vertex with id of "1" exists and if not then throw an exception
g.mergeV([(T.id): 1]).
    option(onCreate, fail("vertex did not exist")).
    option(onMatch, [modified: 2021])

mergeE() with incident Vertex checks

mergeE() has similar semantics as mergeV() but includes the availability of Direction in Map arguments

// uses Direction to specify incident edges
g.mergeE([(T.label): 'knows', weight: 0.5, 
          (OUT): new ReferenceVertex(1, 'person'), 
          (IN): new ReferenceVertex(2, 'person')])

// the Vertex for the edge is defaulted to the incoming traverser and the match
// criteria assumes IN/OUT of the edge to be that Vertex (thus a self relationship
// in the following example).
g.V().has('person','name','josh').
  mergeE([(T.label): 'self', weight:0.5])

// the above could could also be accomplished explicitly as follows
g.mergeE([(T.label): 'self', weight:0.5, (BOTH): new ReferenceVertex(2, 'person')])

// override the default Vertex of "josh" for IN to indicate an edge josh-knows->vadas
g.V().has('person','name','josh').
  mergeE([(T.label): 'knows', weight:0.5, (IN): new ReferenceVertex(2, 'person')]).
    option(onMatch, ['weight':0.6])
  
// if the match Vertex does not exist then mergeE() will trigger onCreate to construct the
// missing Vertex with addV() and thus allow the Edge to be created.
g.V().has('person','name','josh').
  mergeE([(T.label): 'knows', weight:0.5, (IN): new ReferenceVertex(2000, 'person')]).
    option(onMatch, ['weight':0.6])    
    
// if the previous behavior of adding the missing Vertex is not desired then the traversal
// should be written more defensively
g.V(2000).
  V().has('person','name','josh').
  mergeE([(T.label): 'knows', weight:0.5, (IN): new ReferenceVertex(2000, 'person')]).
    option(onMatch, ['weight':0.6])  
@spmallette
Copy link
Author

// current upsert method with maps:

gremlin> g = TinkerGraph.open().traversal()
==>graphtraversalsource[tinkergraph[vertices:0 edges:0], standard]
gremlin> objects = [[name: "marko"], [name:"marko"]]
==>[name:marko]
==>[name:marko]
gremlin> g.withSideEffect('o',objects).
......1>   V().has('person','name',within(objects.collect{it.name})).aggregate('exists').
......2>   fold().
......3>   select('o').unfold().as('m').
......4>   coalesce(select('exists').unfold().as('p').where('m',eq('p')).by('name'),
......5>            addV("person").property("name", __.select("name")).aggregate('exists')).iterate()
gremlin> g.V()
==>v[0]

@isohrab
Copy link

isohrab commented Jul 12, 2023

Hi Stephan,

would you please write some examples in python?

Thank you in advance

@spmallette
Copy link
Author

There's not much difference really between groovy and python if you know the differences, for example, this:

g.mergeV([(T.id): 1]).
    option(Merge.onCreate, [(T.label): 'person', name: 'marko', age: 29]).
    option(Merge.onMatch, [age: 30])

becomes:

g.merge_v({T.id: 1}),
    option(Merge.on_create, {T.label: 'person', 'name': 'marko', 'age': 29}).
    option(Merge.on_match, {'age': 30})

@isohrab
Copy link

isohrab commented Jul 12, 2023

Thank you so much for your answer.

unfortunately I'm getting following error:

io.netty.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.handler.codec.DecoderException: java.lang.NullPointerException
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:98)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
	at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
	...

for local development I'm using Janusgraph 0.6.3 and gremlin python v 3.6.3.

Would you please tell me what is the best db for local development?

I'm really appreciate your help.

@spmallette
Copy link
Author

0.6.3 does not support mergeV() step as that version is still on TinkerPop 3.5.5. I believe their latest release candidate is on 3.6.x. I think that local development is best done on TinkerGraph. If you have further questions, please post them with the "gremlin" tag" on StackOverflow or join the TinkerPop Discord and ask there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment