This is an automated email from the ASF dual-hosted git repository. spmallette pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tinkerpop.git
commit fa76d3bb0f428213ac03255d0cc12ec85df9ad40 Merge: 5030841548 19b66a6fb5 Author: Stephen Mallette <[email protected]> AuthorDate: Tue Sep 2 08:52:53 2025 -0400 Merge branch '3.8-dev' CHANGELOG.asciidoc | 2 + .../structure/io/binary/types/PathSerializer.java | 4 +- .../io/graphson/GraphSONSerializersV2.java | 11 ++- .../io/graphson/GraphSONSerializersV3.java | 10 ++- .../DriverRemoteConnection/GraphTraversalTests.cs | 47 +++++++++++ gremlin-go/driver/connection_test.go | 54 ++++++++++++ .../gremlin-javascript/lib/structure/graph.ts | 16 ++-- .../test/integration/client-tests.js | 70 ++++++++++++++-- .../test/integration/traversal-test.js | 73 +++++++++++++++++ .../test/unit/graphbinary/PathSerializer-test.js | 92 +++++++++++++++++++++ .../gremlin-javascript/test/unit/graphson-test.js | 38 +++++++-- .../tests/driver/test_driver_remote_connection.py | 15 ++++ .../apache/tinkerpop/gremlin/server/Context.java | 8 +- .../server/GremlinResultSetIntegrateTest.java | 2 +- .../GremlinServerSerializationIntegrateTest.java | 95 +++++++++++++++++++++- .../gremlin/structure/SerializationTest.java | 29 +++---- .../org/apache/tinkerpop/gremlin/util/Tokens.java | 1 - 17 files changed, 512 insertions(+), 55 deletions(-) diff --cc gremlin-go/driver/connection_test.go index 88af1ead0c,153084dbb7..c0b80fde03 --- a/gremlin-go/driver/connection_test.go +++ b/gremlin-go/driver/connection_test.go @@@ -820,18 -1266,59 +820,72 @@@ func TestConnection(t *testing.T) assert.True(t, ok) assert.Equal(t, 0, len(properties)) } + + // Path elements should also have no materialized properties when tokens is set + r, err := g.With("materializeProperties", MaterializeProperties.Tokens).V().Has("person", "name", "marko").OutE().InV().HasLabel("software").Path().Next() + assert.Nil(t, err) + p, err := r.GetPath() + assert.Nil(t, err) + assert.NotNil(t, p) + assert.Equal(t, 3, len(p.Objects)) + + // first element should be a Vertex + if a, ok := p.Objects[0].(*Vertex); assert.True(t, ok) { + props, ok := a.Properties.([]interface{}) + assert.True(t, ok) + assert.Equal(t, 0, len(props)) + } + // second element should be an Edge + if b, ok := p.Objects[1].(*Edge); assert.True(t, ok) { + props, ok := b.Properties.([]interface{}) + assert.True(t, ok) + assert.Equal(t, 0, len(props)) + } + // third element should be a Vertex + if c, ok := p.Objects[2].(*Vertex); assert.True(t, ok) { + props, ok := c.Properties.([]interface{}) + assert.True(t, ok) + assert.Equal(t, 0, len(props)) + } + + // Path elements should have materialized properties when all is set + r, err = g.With("materializeProperties", MaterializeProperties.All).V().Has("person", "name", "marko").OutE().InV().HasLabel("software").Path().Next() + assert.Nil(t, err) + p, err = r.GetPath() + assert.Nil(t, err) + assert.NotNil(t, p) + assert.Equal(t, 3, len(p.Objects)) + + // first element should be a Vertex with properties present + if a, ok := p.Objects[0].(*Vertex); assert.True(t, ok) { + props, ok := a.Properties.([]interface{}) + assert.True(t, ok) + assert.Greater(t, len(props), 0) + } + // second element should be an Edge with properties present + if b, ok := p.Objects[1].(*Edge); assert.True(t, ok) { + props, ok := b.Properties.([]interface{}) + assert.True(t, ok) + assert.Greater(t, len(props), 0) + } + // third element should be a Vertex with properties present + if c, ok := p.Objects[2].(*Vertex); assert.True(t, ok) { + props, ok := c.Properties.([]interface{}) + assert.True(t, ok) + assert.Greater(t, len(props), 0) + } }) } + +func submitCount(i int, client *Client, t *testing.T) { + resultSet, err := client.Submit("g.V().count().as('c').math('c + " + strconv.Itoa(i) + "')") + assert.Nil(t, err) + assert.NotNil(t, resultSet) + result, ok, err := resultSet.One() + assert.Nil(t, err) + assert.True(t, ok) + assert.NotNil(t, result) + c, err := result.GetInt() + assert.Equal(t, 6+i, c) + _, _ = fmt.Fprintf(os.Stdout, "Received result : %s\n", result) +} diff --cc gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/graph.ts index 6f60b728a6,0000000000..117ba617eb mode 100644,000000..100644 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/graph.ts +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/lib/structure/graph.ts @@@ -1,211 -1,0 +1,217 @@@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * @author Jorge Bay Gondra + */ + +import { GraphTraversalSource } from '../process/graph-traversal.js'; +import { TraversalStrategies } from '../process/traversal-strategy.js'; + +/** + * An "empty" graph object to server only as a reference. + */ +export class Graph { + /** + * Returns the graph traversal source. + * @param TraversalSourceClass The constructor to use for the {@code GraphTraversalSource} instance. + * @deprecated As of release 3.3.5, replaced by the traversal() anonymous function. + */ + traversal(TraversalSourceClass: typeof GraphTraversalSource = GraphTraversalSource): GraphTraversalSource { + return new TraversalSourceClass(this, new TraversalStrategies()); + } + + toString() { + return 'graph[]'; + } +} + +class Element<TLabel extends string = string, TId = any> { + constructor( + readonly id: TId, + readonly label: TLabel, + ) {} + + /** + * Compares this instance to another and determines if they can be considered as equal. + */ + equals(other: any): boolean { + return other instanceof Element && this.id === other.id; + } +} + +export class Vertex< + TLabel extends string = string, + TProperties extends Record<string, any> = Record<string, any>, + TId = number, + TVertexProperties = { + [P in keyof TProperties]: P extends string ? VertexProperties<P, TProperties[P]> : never; + }, +> extends Element<TLabel, TId> { + constructor( + id: TId, + label: TLabel, + readonly properties?: TVertexProperties, + ) { + super(id, label); + } + + toString() { + return `v[${this.id}]`; + } +} + +export class Edge< + TOutVertex extends Vertex = Vertex, + TLabel extends string = string, + TInVertex extends Vertex = Vertex, + TProperties extends Record<string, any> = Record<string, any>, + TId = number, +> extends Element<TLabel, TId> { + constructor( + id: TId, + readonly outV: TOutVertex, + readonly label: TLabel, + readonly inV: TInVertex, + readonly properties: TProperties = {} as TProperties, + ) { + super(id, label); + if (properties) { - const keys = Object.keys(properties); - for (let i = 0; i < keys.length; i++) { - const k = keys[i]; - this.properties![k as keyof TProperties] = properties[k].value; - } ++ if (Array.isArray(properties)) { ++ // Handle array of Property objects ++ properties.forEach((prop) => { ++ // Use type assertion to inform TypeScript that prop.key is a valid key for TProperties ++ (this.properties as any)[prop.key] = prop.value; ++ }); ++ } else { ++ // Handle object format as before ++ Object.keys(properties).forEach((k) => { ++ (this.properties as any)[k] = properties[k].value; ++ }); + } + } + + toString() { + const outVId = this.outV ? this.outV.id : '?'; + const inVId = this.inV ? this.inV.id : '?'; + + return `e[${this.id}][${outVId}-${this.label}->${inVId}]`; + } +} + +export class VertexProperty< + TLabel extends string = string, + TValue = any, + TProperties extends Record<string, any> | null | undefined = Record<string, any>, + TId = any, +> extends Element<TLabel, TId> { + readonly key: string; + + constructor( + id: TId, + label: TLabel, + readonly value: TValue, + readonly properties?: TProperties, + ) { + super(id, label); + this.value = value; + this.key = this.label; + this.properties = properties; + } + + toString() { + return `vp[${this.label}->${summarize(this.value)}]`; + } +} + +export type VertexProperties< + TLabel extends string = string, + TValue = any, + TProperties extends Record<string, any> = Record<string, any>, + TId = any, +> = [VertexProperty<TLabel, TValue, TProperties, TId>, ...Array<VertexProperty<TLabel, TValue, TProperties, TId>>]; + +export class Property<T = any> { + constructor( + readonly key: string, + readonly value: T, + ) {} + + toString() { + return `p[${this.key}->${summarize(this.value)}]`; + } + + equals(other: any) { + return other instanceof Property && this.key === other.key && this.value === other.value; + } +} + +/** + * Represents a walk through a graph as defined by a traversal. + */ +export class Path { + constructor( + readonly labels: string[], + readonly objects: any[], + ) {} + + toString() { + return `path[${(this.objects || []).join(', ')}]`; + } + + equals(other: any) { + if (!(other instanceof Path)) { + return false; + } + if (other === this) { + return true; + } + return areEqual(this.objects, other.objects) && areEqual(this.labels, other.labels); + } +} + +function areEqual(obj1: any, obj2: any) { + if (obj1 === obj2) { + return true; + } + if (typeof obj1.equals === 'function') { + return obj1.equals(obj2); + } + if (Array.isArray(obj1) && Array.isArray(obj2)) { + if (obj1.length !== obj2.length) { + return false; + } + for (let i = 0; i < obj1.length; i++) { + if (!areEqual(obj1[i], obj2[i])) { + return false; + } + } + return true; + } + return false; +} + +function summarize(value: any) { + if (value === null || value === undefined) { + return value; + } + + const strValue = value.toString(); + return strValue.length > 20 ? strValue.substr(0, 20) : strValue; +} diff --cc gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js index 96a62bcde7,a61701d255..b50b645e55 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/integration/client-tests.js @@@ -17,17 -17,20 +17,18 @@@ * under the License. */ -'use strict'; - -const assert = require('assert'); -const Bytecode = require('../../lib/process/bytecode'); -const graphModule = require('../../lib/structure/graph'); -const helper = require('../helper'); -const t = require('../../lib/process/traversal'); +import assert from 'assert'; +import Bytecode from '../../lib/process/bytecode.js'; +import { Vertex } from '../../lib/structure/graph.js'; +import { getClient } from '../helper.js'; +import { cardinality } from '../../lib/process/traversal.js'; - let client; + let client, clientCrew; describe('Client', function () { before(function () { - client = helper.getClient('gmodern'); + client = getClient('gmodern'); + clientCrew = helper.getClient('gcrew') return client.open(); }); after(function () { @@@ -79,11 -83,13 +81,13 @@@ assert.ok(result); assert.strictEqual(result.length, 1); const vertex = result.first().object; - assert.ok(vertex instanceof graphModule.Vertex); + assert.ok(vertex instanceof Vertex); let age, name if (vertex.properties instanceof Array) { - age = vertex.properties[1] - name = vertex.properties[0] + const ageProps = vertex.properties.filter(p => p.key === 'age'); + const nameProps = vertex.properties.filter(p => p.key === 'name'); + age = ageProps[0]; + name = nameProps[0]; } else { age = vertex.properties.age[0] name = vertex.properties.name[0] @@@ -110,11 -118,14 +116,14 @@@ assert.ok(result); assert.strictEqual(result.length, 1); const vertex = result.first(); - assert.ok(vertex instanceof graphModule.Vertex); + assert.ok(vertex instanceof Vertex); + // if/then until TINKERPOP-3186 let age, name if (vertex.properties instanceof Array) { - age = vertex.properties[1] - name = vertex.properties[0] + const ageProps = vertex.properties.filter(p => p.key === 'age'); + const nameProps = vertex.properties.filter(p => p.key === 'name'); + age = ageProps[0]; + name = nameProps[0]; } else { age = vertex.properties.age[0] name = vertex.properties.name[0] diff --cc gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js index 705a4836c2,913102b098..804c565166 --- a/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js +++ b/gremlin-javascript/src/main/javascript/gremlin-javascript/test/unit/graphson-test.js @@@ -123,12 -129,28 +123,28 @@@ describe('GraphSONReader', function () assert.ok(result.objects); assert.ok(result.labels); assert.strictEqual(result.objects[2], 'lop'); - assert.ok(result.objects[0] instanceof Vertex); - assert.ok(result.objects[1] instanceof Vertex); - assert.strictEqual(result.objects[0].label, 'person'); - assert.strictEqual(result.objects[1].label, 'software'); + const a = result.objects[0]; + const bc = result.objects[1]; - assert.ok(a instanceof graph.Vertex); - assert.ok(bc instanceof graph.Vertex); ++ assert.ok(a instanceof Vertex); ++ assert.ok(bc instanceof Vertex); + assert.strictEqual(a.label, 'person'); + assert.strictEqual(bc.label, 'software'); + assert.ok(a.properties); + assert.ok(a.properties['name']); + assert.strictEqual(a.properties['name'].length, 1); + assert.strictEqual(a.properties['name'][0].value, 'marko'); + assert.ok(a.properties['age']); + assert.strictEqual(a.properties['age'].length, 1); + assert.strictEqual(a.properties['age'][0].value, 29); + assert.ok(bc.properties); + assert.ok(bc.properties['name']); + assert.strictEqual(bc.properties['name'].length, 1); + assert.strictEqual(bc.properties['name'][0].value, 'lop'); + assert.ok(bc.properties['lang']); + assert.strictEqual(bc.properties['lang'].length, 1); + assert.strictEqual(bc.properties['lang'][0].value, 'java'); }); - it('should parse paths from GraphSON3', function () { + it('should parse paths from GraphSON3 without properties', function () { const obj = { "@type" : "g:Path", "@value" : { @@@ -175,10 -197,14 +191,14 @@@ assert.ok(result.objects); assert.ok(result.labels); assert.strictEqual(result.objects[2], 'lop'); - assert.ok(result.objects[0] instanceof Vertex); - assert.ok(result.objects[1] instanceof Vertex); - assert.strictEqual(result.objects[0].label, 'person'); - assert.strictEqual(result.objects[1].label, 'software'); + const a = result.objects[0]; + const bc = result.objects[1]; - assert.ok(a instanceof graph.Vertex); - assert.ok(bc instanceof graph.Vertex); ++ assert.ok(a instanceof Vertex); ++ assert.ok(bc instanceof Vertex); + assert.strictEqual(a.label, 'person'); + assert.strictEqual(bc.label, 'software'); + assert.ok(a.properties === undefined); + assert.ok(bc.properties === undefined); }); }); describe('GraphSONWriter', function () { diff --cc gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py index bce31840bd,346d5ffbb3..f33ebfdc3f --- a/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py +++ b/gremlin-python/src/main/python/tests/driver/test_driver_remote_connection.py @@@ -205,6 -127,32 +205,21 @@@ class TestDriverRemoteConnection(object results = g.with_("materializeProperties", "tokens").V().properties().to_list() for vp in results: assert vp.properties is None or len(vp.properties) == 0 + # # + # test materializeProperties in Path - GraphSON will deserialize into None and GraphBinary to [] - p = g.with_("materializeProperties", "tokens").V().has('name', 'marko').outE().inV().has_label('software').path().next() - assert 3 == len(p.objects) - assert p.objects[0].properties is None or len(p.objects[0].properties) == 0 - assert p.objects[1].properties is None or len(p.objects[1].properties) == 0 - assert p.objects[2].properties is None or len(p.objects[2].properties) == 0 ++ # p = g.with_("materializeProperties", "tokens").V().has('name', 'marko').outE().inV().has_label('software').path().next() ++ # assert 3 == len(p.objects) ++ # assert p.objects[0].properties is None or len(p.objects[0].properties) == 0 ++ # assert p.objects[1].properties is None or len(p.objects[1].properties) == 0 ++ # assert p.objects[2].properties is None or len(p.objects[2].properties) == 0 + # # + # test materializeProperties in Path - 'all' should materialize properties on each element - p = g.with_("materializeProperties", "all").V().has('name', 'marko').outE().inV().has_label('software').path().next() - assert 3 == len(p.objects) - assert p.objects[0].properties is not None and len(p.objects[0].properties) > 0 - # edges have dict-like properties; ensure not empty - assert p.objects[1].properties is not None and len(p.objects[1].properties) > 0 - assert p.objects[2].properties is not None and len(p.objects[2].properties) > 0 - - def test_lambda_traversals(self, remote_connection): - statics.load_statics(globals()) - assert "remoteconnection[{},gmodern]".format(test_no_auth_url) == str(remote_connection) - g = traversal().with_(remote_connection) - - assert 24.0 == g.withSack(1.0, lambda: ("x -> x + 1", "gremlin-groovy")).V().both().sack().sum_().next() - assert 24.0 == g.withSack(lambda: ("{1.0d}", "gremlin-groovy"), lambda: ("x -> x + 1", "gremlin-groovy")).V().both().sack().sum_().next() - - assert 48.0 == g.withSack(1.0, lambda: ("x, y -> x + y + 1", "gremlin-groovy")).V().both().sack().sum_().next() - assert 48.0 == g.withSack(lambda: ("{1.0d}", "gremlin-groovy"), lambda: ("x, y -> x + y + 1", "gremlin-groovy")).V().both().sack().sum_().next() ++ # p = g.with_("materializeProperties", "all").V().has('name', 'marko').outE().inV().has_label('software').path().next() ++ # assert 3 == len(p.objects) ++ # assert p.objects[0].properties is not None and len(p.objects[0].properties) > 0 ++ # # edges have dict-like properties; ensure not empty ++ # assert p.objects[1].properties is not None and len(p.objects[1].properties) > 0 ++ # assert p.objects[2].properties is not None and len(p.objects[2].properties) > 0 def test_iteration(self, remote_connection): statics.load_statics(globals()) diff --cc gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java index 9c35e8864f,b0eb6aedbe..d4ce01b68b --- a/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java +++ b/gremlin-server/src/main/java/org/apache/tinkerpop/gremlin/server/Context.java @@@ -18,13 -18,9 +18,14 @@@ */ package org.apache.tinkerpop.gremlin.server; + import org.apache.tinkerpop.gremlin.process.traversal.Path; +import io.netty.channel.ChannelHandlerContext; +import org.apache.tinkerpop.gremlin.groovy.engine.GremlinExecutor; +import org.apache.tinkerpop.gremlin.jsr223.GremlinScriptChecker; import org.apache.tinkerpop.gremlin.process.traversal.traverser.util.AbstractTraverser; +import org.apache.tinkerpop.gremlin.server.handler.HttpGremlinEndpointHandler; import org.apache.tinkerpop.gremlin.structure.Element; +import org.apache.tinkerpop.gremlin.structure.Graph; import org.apache.tinkerpop.gremlin.structure.util.reference.ReferenceFactory; import org.apache.tinkerpop.gremlin.util.Tokens; import org.apache.tinkerpop.gremlin.util.message.RequestMessage; @@@ -196,8 -328,13 +197,13 @@@ public class Context final Object firstElement = aggregate.get(0); if (firstElement instanceof Element) { - for (int i = 0; i < aggregate.size(); i++) + for (int i = 0; i < aggregate.size(); i++) { - aggregate.set(i, ReferenceFactory.detach((Element) aggregate.get(i))); + aggregate.set(i, ReferenceFactory.detach(aggregate.get(i))); + } + } else if (firstElement instanceof Path) { + for (int i = 0; i < aggregate.size(); i++) { + aggregate.set(i, ReferenceFactory.detach((Path) aggregate.get(i))); + } } else if (firstElement instanceof AbstractTraverser) { for (final Object item : aggregate) ((AbstractTraverser) item).detach(); diff --cc gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java index 6d6a1c804d,0030f913f0..32ccead100 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinResultSetIntegrateTest.java @@@ -144,10 -170,9 +144,10 @@@ public class GremlinResultSetIntegrateT @Test public void shouldHandlePathResult() throws Exception { - final ResultSet results = client.submit("gmodern.V().out().path()"); + RequestOptions options = RequestOptions.build().addG("gmodern").create(); + final ResultSet results = client.submit("g.V().out().path()", options); final Path p = results.all().get().get(0).getPath(); - assertThat(p, instanceOf(ReferencePath.class)); + assertThat(p, instanceOf(Path.class)); } @Test diff --cc gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java index ec5b0956a9,cca1b80394..ff15279133 --- a/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java +++ b/gremlin-server/src/test/java/org/apache/tinkerpop/gremlin/server/GremlinServerSerializationIntegrateTest.java @@@ -20,17 -20,20 +20,19 @@@ package org.apache.tinkerpop.gremlin.se import org.apache.tinkerpop.gremlin.driver.Client; import org.apache.tinkerpop.gremlin.driver.Cluster; +import org.apache.tinkerpop.gremlin.driver.RequestOptions; import org.apache.tinkerpop.gremlin.driver.remote.DriverRemoteConnection; import org.apache.tinkerpop.gremlin.process.traversal.AnonymousTraversalSource; + import org.apache.tinkerpop.gremlin.process.traversal.Path; import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource; import org.apache.tinkerpop.gremlin.structure.Edge; import org.apache.tinkerpop.gremlin.structure.Property; import org.apache.tinkerpop.gremlin.structure.Vertex; import org.apache.tinkerpop.gremlin.structure.VertexProperty; + import org.apache.tinkerpop.gremlin.util.Tokens; import org.apache.tinkerpop.gremlin.util.iterator.IteratorUtils; import org.apache.tinkerpop.gremlin.util.ser.AbstractMessageSerializer; -import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1; -import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2; -import org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3; +import org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV4; import org.junit.After; import org.junit.Before; import org.junit.Test; @@@ -59,10 -62,12 +65,10 @@@ public class GremlinServerSerialization this.serializer = serializer; } - @Parameterized.Parameters + @Parameterized.Parameters(name = "{0}") public static Collection serializers() { return Arrays.asList(new Object[][]{ - {new GraphBinaryMessageSerializerV1()}, - {new GraphSONMessageSerializerV3()}, - {new GraphSONMessageSerializerV2()} + {new GraphBinaryMessageSerializerV4()} }); }
