Skip to content

Commit fb7dfdf

Browse files
committed
Added feature test and docs
1 parent a3aeb24 commit fb7dfdf

7 files changed

Lines changed: 66 additions & 0 deletions

File tree

CHANGELOG.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
3636
* Added `reuseConnectionsForSessions` to Java GLV settings to decide whether to use `SessionedChildClient` for remote transactions.
3737
* Added support for Node 22 and 24 alongside Node 20.
3838
* Fixed `cap()` step throwing an error when used mid-traversal in OLAP.
39+
* Fixed conjoin has incorrect null handling.
3940
4041
[[release-3-7-5]]
4142
=== TinkerPop 3.7.5 (Release Date: November 12, 2025)

docs/src/upgrade/release-3.7.x.asciidoc

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,31 @@ See: link:https://issues.apache.org/jira/browse/TINKERPOP-3235[TINKERPOP-3235]
5959
6060
Gremlin Javascript now supports Node 22 and 24 alongside Node 20.
6161
62+
==== conjoin() Step Null Handling
63+
64+
The `conjoin()` step previously returned `null` when elements in the incoming list are `null`. This behavior has
65+
been changed so that `conjoin()` now returns an empty string (`""`) in that case.
66+
67+
[source,groovy]
68+
----
69+
// 3.7.5
70+
gremlin> g.inject([null]).conjoin("-")
71+
==>null
72+
gremlin> g.inject([null, null]).conjoin("-")
73+
==>null
74+
75+
// 3.7.6
76+
gremlin> g.inject([null]).conjoin("+")
77+
==>
78+
gremlin> g.inject([null, null]).conjoin("+")
79+
==>
80+
----
81+
82+
Code that checks the result of `conjoin()` for lists that include `null` elements should be updated to check for
83+
an empty string instead.
84+
85+
See: link:https://issues.apache.org/jira/browse/TINKERPOP-3225[TINKERPOP-3225]
86+
6287
=== Upgrading for Providers
6388
6489
==== Graph System Providers

gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Gremlin.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<s
170170
{"g_injectX7X_anyXeqX7XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(7).Any(P.Eq(7))}},
171171
{"g_injectXnull_nullX_anyXeqXnullXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Any(P.Eq(null))}},
172172
{"g_injectX3_threeX_anyXeqX3XX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Any(P.Eq(3))}},
173+
{"g_V_coinX1_0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(1.0)}},
173174
{"g_V_coinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(1.0)}},
174175
{"g_V_coinX0X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Coin(0.0)}},
175176
{"g_withStrategiesXSeedStrategyX_V_order_byXnameX_coinX50X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.WithStrategies(new SeedStrategy(seed: 999999)).V().Order().By("name").Coin(0.5)}},
@@ -632,6 +633,8 @@ private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<s
632633
{"g_V_out_out_path_byXnameX_conjoinXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Out().Path().By("name").Conjoin("")}},
633634
{"g_injectXa_null_bX_conjoinXxyzX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("xyz")}},
634635
{"g_injectX3_threeX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin(";")}},
636+
{"g_injectXnull_a_null_bX_fold_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}},
637+
{"g_injectXnull_nullX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}},
635638
{"g_V_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
636639
{"g_V_dedup_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Dedup().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
637640
{"g_V_hasLabelXsoftwareX_connectedComponent_project_byXnameX_byXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").ConnectedComponent().Project<object>("name","component").By("name").By("gremlin.connectedComponentVertexProgram.component")}},

gremlin-go/driver/cucumber/gremlin.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
141141
"g_injectX7X_anyXeqX7XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(7).Any(gremlingo.P.Eq(7))}},
142142
"g_injectXnull_nullX_anyXeqXnullXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Any(gremlingo.P.Eq(nil))}},
143143
"g_injectX3_threeX_anyXeqX3XX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Any(gremlingo.P.Eq(3))}},
144+
"g_V_coinX1_0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(1.0)}},
144145
"g_V_coinX1X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(1.0)}},
145146
"g_V_coinX0X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Coin(0.0)}},
146147
"g_withStrategiesXSeedStrategyX_V_order_byXnameX_coinX50X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.WithStrategies(gremlingo.SeedStrategy(gremlingo.SeedStrategyConfig{Seed: 999999})).V().Order().By("name").Coin(0.5)}},
@@ -603,6 +604,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
603604
"g_V_out_out_path_byXnameX_conjoinXX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Out().Out().Path().By("name").Conjoin("")}},
604605
"g_injectXa_null_bX_conjoinXxyzX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Conjoin("xyz")}},
605606
"g_injectX3_threeX_conjoinX_X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Conjoin(";")}},
607+
"g_injectXnull_a_null_bX_fold_conjoinXplusX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Conjoin("+")}},
608+
"g_injectXnull_nullX_conjoinXplusX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject(p["xx1"]).Conjoin("+")}},
606609
"g_V_connectedComponent_hasXcomponentX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
607610
"g_V_dedup_connectedComponent_hasXcomponentX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Dedup().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
608611
"g_V_hasLabelXsoftwareX_connectedComponent_project_byXnameX_byXcomponentX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().HasLabel("software").ConnectedComponent().Project("name", "component").By("name").By("gremlin.connectedComponentVertexProgram.component")}},

gremlin-javascript/src/main/javascript/gremlin-javascript/test/cucumber/gremlin.js

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gremlin-python/src/main/python/tests/feature/gremlin.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@
143143
'g_injectX7X_anyXeqX7XX': [(lambda g:g.inject(7).any_(P.eq(7)))],
144144
'g_injectXnull_nullX_anyXeqXnullXX': [(lambda g, xx1=None:g.inject(xx1).any_(P.eq(None)))],
145145
'g_injectX3_threeX_anyXeqX3XX': [(lambda g, xx1=None:g.inject(xx1).any_(P.eq(3)))],
146+
'g_V_coinX1_0X': [(lambda g:g.V().coin(float(1.0)))],
146147
'g_V_coinX1X': [(lambda g:g.V().coin(float(1.0)))],
147148
'g_V_coinX0X': [(lambda g:g.V().coin(float(0.0)))],
148149
'g_withStrategiesXSeedStrategyX_V_order_byXnameX_coinX50X': [(lambda g:g.with_strategies(*[TraversalStrategy('SeedStrategy',{'seed':999999,'strategy':'org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy'}, 'org.apache.tinkerpop.gremlin.process.traversal.strategy.decoration.SeedStrategy')]).V().order().by('name').coin(float(0.5)))],
@@ -605,6 +606,8 @@
605606
'g_V_out_out_path_byXnameX_conjoinXX': [(lambda g:g.V().out().out().path().by('name').conjoin(''))],
606607
'g_injectXa_null_bX_conjoinXxyzX': [(lambda g, xx1=None:g.inject(xx1).conjoin('xyz'))],
607608
'g_injectX3_threeX_conjoinX_X': [(lambda g, xx1=None:g.inject(xx1).conjoin(';'))],
609+
'g_injectXnull_a_null_bX_fold_conjoinXplusX': [(lambda g, xx1=None:g.inject(xx1).conjoin('+'))],
610+
'g_injectXnull_nullX_conjoinXplusX': [(lambda g, xx1=None:g.inject(xx1).conjoin('+'))],
608611
'g_V_connectedComponent_hasXcomponentX': [(lambda g:g.V().connected_component().has('gremlin.connectedComponentVertexProgram.component'))],
609612
'g_V_dedup_connectedComponent_hasXcomponentX': [(lambda g:g.V().dedup().connected_component().has('gremlin.connectedComponentVertexProgram.component'))],
610613
'g_V_hasLabelXsoftwareX_connectedComponent_project_byXnameX_byXcomponentX': [(lambda g:g.V().has_label('software').connected_component().project('name','component').by('name').by('gremlin.connectedComponentVertexProgram.component'))],
@@ -1456,6 +1459,7 @@
14561459
'g_withSideEffectXa_xx1_addAllX_V_aggregateXlocal_aX_byXageX_capXaX': [(lambda g, xx1=None:g.with_side_effect('a',xx1,Operator.add_all).V().aggregate(Scope.local,'a').by('age').cap('a'))],
14571460
'g_withSideEffectXa_xx1_assignX_V_aggregateXaX_byXageX_capXaX': [(lambda g, xx1=None:g.with_side_effect('a',xx1,Operator.assign).V().aggregate('a').by('age').cap('a'))],
14581461
'g_withSideEffectXa_xx1_assignX_V_order_byXageX_aggregateXlocal_aX_byXageX_capXaX': [(lambda g, xx1=None:g.with_side_effect('a',xx1,Operator.assign).V().order().by('age').aggregate(Scope.local,'a').by('age').cap('a'))],
1462+
'g_V_repeatXaggregateXaXX_timesX2X_capXaX_unfold': [(lambda g:g.V().repeat(__.aggregate('a')).times(2).cap('a').unfold())],
14591463
'g_V_aggregateXaX_capXaX_unfold_both': [(lambda g:g.V().aggregate('a').cap('a').unfold().both())],
14601464
'g_V_aggregateXaX_capXaX_unfold_barrier_both': [(lambda g:g.V().aggregate('a').cap('a').unfold().barrier().both())],
14611465
'g_V_fail': [(lambda g:g.V().fail())],

gremlin-test/src/main/resources/org/apache/tinkerpop/gremlin/test/features/map/Conjoin.feature

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,3 +153,29 @@ Feature: Step - conjoin()
153153
Then the result should be unordered
154154
| result |
155155
| 3;three |
156+
157+
@GraphComputerVerificationInjectionNotSupported
158+
Scenario: g_injectXnull_a_null_bX_fold_conjoinXplusX
159+
Given the empty graph
160+
And using the parameter xx1 defined as "l[null,a,null,b]"
161+
And the traversal of
162+
"""
163+
g.inject(xx1).conjoin("+")
164+
"""
165+
When iterated to list
166+
Then the result should be unordered
167+
| result |
168+
| str[a+b] |
169+
170+
@GraphComputerVerificationInjectionNotSupported
171+
Scenario: g_injectXnull_nullX_conjoinXplusX
172+
Given the empty graph
173+
And using the parameter xx1 defined as "l[null,null]"
174+
And the traversal of
175+
"""
176+
g.inject(xx1).conjoin("+")
177+
"""
178+
When iterated to list
179+
Then the result should be unordered
180+
| result |
181+
| str[] |

0 commit comments

Comments
 (0)