Skip to content

Commit e3be426

Browse files
committed
Merge branch '3.8-dev'
2 parents bc9621f + 9509d4f commit e3be426

12 files changed

Lines changed: 118 additions & 4 deletions

File tree

.github/workflows/build-test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ env:
55
# take care when modifying this list because GLVs use shell commands to remove themselves from this list and
66
# modifications could break patterns of replacement they are searching for.
77
EXCLUDE_MODULES: '-:gremlin-dotnet-source,-:gremlin-dotnet-tests,-:gremlin-go,-:gremlin-js,-:gremlin-javascript,-:gremlint,-:gremlin-mcp,-:gremlin-python'
8-
EXCLUDE_FOR_GLV: '-:gremlin-annotations,-:gremlin-console,-:hadoop-gremlin,-:spark-gremlin,-:sparql-gremlin'
8+
EXCLUDE_FOR_GLV: '-:gremlin-console,-:gremlin-coverage,-:hadoop-gremlin,-:spark-gremlin,-:sparql-gremlin'
99
jobs:
1010
smoke:
1111
name: smoke

CHANGELOG.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
338338
[[release-3-7-7]]
339339
=== TinkerPop 3.7.7 (Release Date: NOT OFFICIALLY RELEASED YET)
340340
341+
* Fixed conjoin has incorrect null handling.
342+
341343
[[release-3-7-6]]
342344
=== TinkerPop 3.7.6 (Release Date: April 1, 2026)
343345

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,35 @@ image::gremlin-zamfir.png[width=185]
3030
Please see the link:https://github.com/apache/tinkerpop/blob/3.7.7/CHANGELOG.asciidoc#release-3-7-7[changelog] for a
3131
complete list of all the modifications that are part of this release.
3232
33+
=== Upgrading for Users
34+
35+
==== conjoin() Step Null Handling
36+
37+
The `conjoin()` step previously returned `null` when elements in the incoming list are `null`. This behavior has
38+
been changed so that `conjoin()` now returns an empty string (`""`) in that case.
39+
40+
[source,groovy]
41+
----
42+
// 3.7.6
43+
gremlin> g.inject([null]).conjoin("-")
44+
==>null
45+
gremlin> g.inject([null, null]).conjoin("-")
46+
==>null
47+
48+
// 3.7.7
49+
gremlin> g.inject([null]).conjoin("+")
50+
==>
51+
gremlin> g.inject([null, null]).conjoin("+")
52+
==>
53+
----
54+
55+
Code that checks the result of `conjoin()` for lists that include `null` elements should be updated to check for
56+
an empty string instead.
57+
58+
See: link:https://issues.apache.org/jira/browse/TINKERPOP-3225[TINKERPOP-3225]
59+
60+
61+
3362
== TinkerPop 3.7.6
3463
3564
*Release Date: April 1, 2026*

gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStep.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ protected String map(final Traverser.Admin<S> traverser) {
6767
joinResult.delete(joinResult.length() - delimiter.get().length(), joinResult.length());
6868
return joinResult.toString();
6969
} else {
70-
return null;
70+
return "";
7171
}
7272
}
7373

gremlin-core/src/test/java/org/apache/tinkerpop/gremlin/process/traversal/step/map/ConjoinStepTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ public void testReturnTypes() {
5151
assertEquals("5AA8AA10", __.__(new long[] {5L, 8L, 10L}).conjoin("AA").next());
5252
assertEquals("715", __.__(1).constant(new Long[] {7L, 15L}).conjoin("").next());
5353
assertEquals("5.5,8.0,10.1", __.__(new double[] {5.5, 8.0, 10.1}).conjoin(",").next());
54-
assertNull(__.__(Arrays.asList(null, null)).conjoin(",").next());
54+
assertEquals("", __.__(Arrays.asList(null, null)).conjoin(",").next());
55+
assertEquals("1", __.__(Arrays.asList(null, 1, null)).conjoin(",").next());
5556

5657
final Set<Integer> set = new LinkedHashSet<>();
5758
set.add(10); set.add(11); set.add(12);

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,7 @@ private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<s
10601060
{"g_hasLabelXpersonX_valuesXnameX_asXaX_constantXMrX_concatXselectXaX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("person").Values<object>("name").As("a").Constant<object>("Mr.").Concat(__.Select<object>("a"))}},
10611061
{"g_hasLabelXsoftwareX_asXaX_valuesXnameX_concatXunsesX_concatXselectXaXvaluesXlangX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().HasLabel("software").As("a").Values<object>("name").Concat(" uses ").Concat(__.Select<object>("a").Values<object>("lang"))}},
10621062
{"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_labelX_concatXselectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label()).Concat(__.Select<object>("a").InV().Values<object>("name"))}},
1063+
<<<<<<< HEAD
10631064
{"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_label_selectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label(), __.Select<object>("a").InV().Values<object>("name"))}},
10641065
{"g_addVXconstantXprefix_X_concatXVX1X_labelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV((string) "person").Property("name", "marko").Property("age", 29).As("marko").AddV((string) "person").Property("name", "vadas").Property("age", 27).As("vadas").AddV((string) "software").Property("name", "lop").Property("lang", "java").As("lop").AddV((string) "person").Property("name", "josh").Property("age", 32).As("josh").AddV((string) "software").Property("name", "ripple").Property("lang", "java").As("ripple").AddV((string) "person").Property("name", "peter").Property("age", 35).As("peter").AddE((string) "knows").From("marko").To("vadas").Property("weight", 0.5d).AddE((string) "knows").From("marko").To("josh").Property("weight", 1.0d).AddE((string) "created").From("marko").To("lop").Property("weight", 0.4d).AddE((string) "created").From("josh").To("ripple").Property("weight", 1.0d).AddE((string) "created").From("josh").To("lop").Property("weight", 0.4d).AddE((string) "created").From("peter").To("lop").Property("weight", 0.2d), (g,p) =>g.AddV(__.Constant<object>("prefix_").Concat(__.V(p["vid1"]).Label())).Label()}},
10651066
{"g_injectXnullX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Conjoin((string) "1")}},
@@ -1073,6 +1074,23 @@ private static IDictionary<string, List<Func<GraphTraversalSource, IDictionary<s
10731074
{"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((string) "")}},
10741075
{"g_injectXa_null_bX_conjoinXxyzX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { "a", null, "b" }).Conjoin((string) "xyz")}},
10751076
{"g_injectX3_threeX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(new List<object> { 3, "three" }).Conjoin((string) ";")}},
1077+
=======
1078+
{"g_VX1X_outE_asXaX_VX1X_valuesXnamesX_concatXselectXaX_label_selectXaX_inV_valuesXnameXX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V(p["vid1"]).OutE().As("a").V(p["vid1"]).Values<object>("name").Concat(__.Select<object>("a").Label(),__.Select<object>("a").InV().Values<object>("name"))}},
1079+
{"g_addVXconstantXprefix_X_concatXVX1X_labelX_label", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.AddV("person").Property("name","marko").Property("age",29).As("marko").AddV("person").Property("name","vadas").Property("age",27).As("vadas").AddV("software").Property("name","lop").Property("lang","java").As("lop").AddV("person").Property("name","josh").Property("age",32).As("josh").AddV("software").Property("name","ripple").Property("lang","java").As("ripple").AddV("person").Property("name","peter").Property("age",35).As("peter").AddE("knows").From("marko").To("vadas").Property("weight",0.5).AddE("knows").From("marko").To("josh").Property("weight",1.0).AddE("created").From("marko").To("lop").Property("weight",0.4).AddE("created").From("josh").To("ripple").Property("weight",1.0).AddE("created").From("josh").To("lop").Property("weight",0.4).AddE("created").From("peter").To("lop").Property("weight",0.2), (g,p) =>g.AddV(__.Constant<object>("prefix_").Concat(__.V(p["vid1"]).Label())).Label()}},
1080+
{"g_injectXnullX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject<object>(null).Conjoin("1")}},
1081+
{"g_V_valuesXnameX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Conjoin("1")}},
1082+
{"g_V_valuesXnonexistantX_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("nonexistant").Fold().Conjoin(";")}},
1083+
{"g_V_valuesXnameX_order_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("name").Order().Fold().Conjoin("_")}},
1084+
{"g_V_valuesXageX_order_fold_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Values<object>("age").Order().Fold().Conjoin(";")}},
1085+
{"g_V_out_path_byXvaluesXnameX_toUpperX_conjoinXMARKOX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Out().Path().By(__.Values<object>("name").ToUpper()).Conjoin("MARKO")}},
1086+
{"g_injectXmarkoX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("-")}},
1087+
{"g_V_valueMapXlocationX_selectXvaluesX_unfold_orderXlocalX_conjoinX1X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().ValueMap<object,object>("location").Select<object>(Column.Values).Unfold<object>().Order(Scope.Local).Conjoin("1")}},
1088+
{"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("")}},
1089+
{"g_injectXa_null_bX_conjoinXxyzX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("xyz")}},
1090+
{"g_injectX3_threeX_conjoinX_X", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin(";")}},
1091+
{"g_injectXnull_a_null_bX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}},
1092+
{"g_injectXnull_nullX_conjoinXplusX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.Inject(p["xx1"]).Conjoin("+")}},
1093+
>>>>>>> 3.7-dev
10761094
{"g_V_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
10771095
{"g_V_dedup_connectedComponent_hasXcomponentX", new List<Func<GraphTraversalSource, IDictionary<string, object>, ITraversal>> {(g,p) =>g.V().Dedup().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
10781096
{"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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,8 @@ var translationMap = map[string][]func(g *gremlingo.GraphTraversalSource, p map[
10431043
"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("")}},
10441044
"g_injectXa_null_bX_conjoinXxyzX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{"a", nil, "b"}).Conjoin("xyz")}},
10451045
"g_injectX3_threeX_conjoinX_X": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{int32(3), "three"}).Conjoin(";")}},
1046+
"g_injectXnull_a_null_bX_conjoinXplusX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{nil, "a", nil, "b"}).Conjoin("+")}},
1047+
"g_injectXnull_nullX_conjoinXplusX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.Inject([]interface{}{nil, nil}).Conjoin("+")}},
10461048
"g_V_connectedComponent_hasXcomponentX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
10471049
"g_V_dedup_connectedComponent_hasXcomponentX": {func(g *gremlingo.GraphTraversalSource, p map[string]interface{}) *gremlingo.GraphTraversal {return g.V().Dedup().ConnectedComponent().Has("gremlin.connectedComponentVertexProgram.component")}},
10481050
"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-js/gremlin-javascript/test/cucumber/gremlin.js

Lines changed: 2 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: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1046,6 +1046,8 @@
10461046
'g_V_out_out_path_byXnameX_conjoinXX': [(lambda g:g.V().out().out().path().by('name').conjoin(''))],
10471047
'g_injectXa_null_bX_conjoinXxyzX': [(lambda g:g.inject(['a', None, 'b']).conjoin('xyz'))],
10481048
'g_injectX3_threeX_conjoinX_X': [(lambda g:g.inject([3, 'three']).conjoin(';'))],
1049+
'g_injectXnull_a_null_bX_conjoinXplusX': [(lambda g:g.inject([None, 'a', None, 'b']).conjoin('+'))],
1050+
'g_injectXnull_nullX_conjoinXplusX': [(lambda g:g.inject([None, None]).conjoin('+'))],
10491051
'g_V_connectedComponent_hasXcomponentX': [(lambda g:g.V().connected_component().has('gremlin.connectedComponentVertexProgram.component'))],
10501052
'g_V_dedup_connectedComponent_hasXcomponentX': [(lambda g:g.V().dedup().connected_component().has('gremlin.connectedComponentVertexProgram.component'))],
10511053
'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'))],

gremlin-server/scripts/generate-modern-readonly.groovy

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def globals = [:]
2525
globals << [hook : [
2626
onStartUp: { ctx ->
2727
ctx.logger.info("Loading 'modern' graph data.")
28-
TinkerFactory.generateClassic(graph)
28+
TinkerFactory.generateModern(graph)
2929
}
3030
] as LifeCycleHook]
3131

0 commit comments

Comments
 (0)