This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git


The following commit(s) were added to refs/heads/master by this push:
     new 8e167c7b1 fix(server): reject vertex-property metadata during addV 
(#3178)
8e167c7b1 is described below

commit 8e167c7b1ac84194be3336426dea5a2448269016
Author: Stefan Wang <[email protected]>
AuthorDate: Sun Aug 30 13:00:51 2026 -0400

    fix(server): reject vertex-property metadata during addV (#3178)
    
    Throwing from HugePrimaryKeyStrategy validated eagerly, so an unreachable
    coalesce()/choose() addV() fallback failed before its branch was picked.
    
    Fold only T.key/T.value into addV so PRIMARY_KEY labels keep their primary
    key, leave a metadata-bearing AddPropertyStep in place, and stop folding
    across it. HugeVertex.property() then rejects it, T.id case included.
    
    
    ---------
    
    Signed-off-by: 1fanwang <[email protected]>
---
 .../traversal/optimize/HugePrimaryKeyStrategy.java |  36 ++--
 .../org/apache/hugegraph/core/CoreTestSuite.java   |   1 +
 .../hugegraph/core/PrimaryKeyStrategyCoreTest.java | 185 +++++++++++++++++++++
 3 files changed, 208 insertions(+), 14 deletions(-)

diff --git 
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java
 
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java
index e6fa88083..f371c0f30 100644
--- 
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java
+++ 
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/optimize/HugePrimaryKeyStrategy.java
@@ -19,6 +19,7 @@ package org.apache.hugegraph.traversal.optimize;
 
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.tinkerpop.gremlin.process.traversal.Step;
 import org.apache.tinkerpop.gremlin.process.traversal.Traversal;
@@ -76,26 +77,33 @@ public class HugePrimaryKeyStrategy
                 || propertyStep.getCardinality() == null) {
 
                 Object[] kvs = new Object[2];
-                List<Object> kvList = new LinkedList<>();
-
-                propertyStep.getParameters().getRaw().forEach((k, v) -> {
-                    if (T.key.equals(k)) {
-                        kvs[0] = v.get(0);
-                    } else if (T.value.equals(k)) {
-                        kvs[1] = v.get(0);
+                boolean extraParams = false;
+
+                for (Map.Entry<Object, List<Object>> param :
+                     propertyStep.getParameters().getRaw().entrySet()) {
+                    Object paramKey = param.getKey();
+                    if (T.key.equals(paramKey)) {
+                        kvs[0] = param.getValue().get(0);
+                    } else if (T.value.equals(paramKey)) {
+                        kvs[1] = param.getValue().get(0);
                     } else {
-                        kvList.add(k.toString());
-                        kvList.add(v.get(0));
+                        extraParams = true;
                     }
-                });
+                }
 
                 curAddStep.configure(kvs);
 
-                if (!kvList.isEmpty()) {
-                    curAddStep.configure(kvList.toArray(new Object[0]));
+                if (extraParams) {
+                    /*
+                     * Rejecting here would run for every child traversal, so 
an
+                     * unreachable coalesce()/choose() branch would fail too. 
Leave
+                     * the step for HugeVertex.property() to reject when it 
really
+                     * runs, and stop folding across it.
+                     */
+                    curAddStep = null;
+                } else {
+                    removeSteps.add(step);
                 }
-
-                removeSteps.add(step);
             } else {
                 curAddStep = null;
             }
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
index 230b8d2d0..f18d656c0 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/CoreTestSuite.java
@@ -41,6 +41,7 @@ import org.slf4j.Logger;
         VertexCoreTest.class,
         EdgeCoreTest.class,
         CountStrategyCoreTest.class,
+        PrimaryKeyStrategyCoreTest.class,
         ParentAndSubEdgeCoreTest.class,
         PropertyCoreTest.VertexPropertyCoreTest.class,
         PropertyCoreTest.EdgePropertyCoreTest.class,
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/PrimaryKeyStrategyCoreTest.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/PrimaryKeyStrategyCoreTest.java
new file mode 100644
index 000000000..0561906ce
--- /dev/null
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/PrimaryKeyStrategyCoreTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+package org.apache.hugegraph.core;
+
+import org.apache.hugegraph.schema.SchemaManager;
+import org.apache.hugegraph.testutil.Assert;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
+import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.__;
+import 
org.apache.tinkerpop.gremlin.process.traversal.step.sideEffect.AddPropertyStep;
+import org.apache.tinkerpop.gremlin.structure.T;
+import org.apache.tinkerpop.gremlin.structure.Vertex;
+import org.apache.tinkerpop.gremlin.structure.VertexProperty;
+import org.junit.Test;
+
+public class PrimaryKeyStrategyCoreTest extends BaseCoreTest {
+
+    private static final String LABEL = "person";
+
+    private static final String META_PROPERTIES_ERROR =
+            
VertexProperty.Exceptions.metaPropertiesNotSupported().getMessage();
+    private static final String USER_SUPPLIED_IDS_ERROR =
+            
VertexProperty.Exceptions.userSuppliedIdsNotSupported().getMessage();
+
+    private void initSchema() {
+        SchemaManager schema = graph().schema();
+        schema.propertyKey("name").asText().create();
+        schema.propertyKey("country").asText().create();
+        schema.propertyKey("since").asText().create();
+        schema.vertexLabel(LABEL).properties("name", "country", "since")
+              .primaryKeys("name").nullableKeys("country", "since").create();
+    }
+
+    /**
+     * Applying the strategy must stay side effect free: the metadata-bearing 
step is left in the
+     * traversal so that HugeVertex.property() can reject it when the step 
really runs.
+     */
+    @Test
+    public void testKeepsMetaPropertyStepUnfolded() {
+        this.initSchema();
+
+        GraphTraversal<Vertex, Vertex> traversal = 
graph().traversal().addV(LABEL)
+                                                          .property("country", 
"cn", "since", "2024");
+        traversal.asAdmin().applyStrategies();
+
+        Assert.assertTrue(traversal.asAdmin().getSteps().stream()
+                                   
.anyMatch(AddPropertyStep.class::isInstance));
+        Assert.assertEquals(0L, 
graph().traversal().V().hasLabel(LABEL).count().next());
+    }
+
+    @Test
+    public void testStartStepRejectsMetaProperties() {
+        this.initSchema();
+
+        GraphTraversal<Vertex, Vertex> traversal = 
graph().traversal().addV(LABEL)
+                                                          .property("name", 
"marko")
+                                                          .property("country", 
"cn", "since", "2024");
+
+        this.assertRejectedWhenExecuted(traversal, META_PROPERTIES_ERROR);
+    }
+
+    @Test
+    public void testMidTraversalStepRejectsMetaProperties() {
+        this.initSchema();
+
+        GraphTraversal<Integer, Vertex> traversal = 
graph().traversal().inject(1).addV(LABEL)
+                                                           .property("name", 
"marko")
+                                                           
.property("country", "cn", "since", "2024");
+
+        this.assertRejectedWhenExecuted(traversal, META_PROPERTIES_ERROR);
+    }
+
+    /**
+     * The metadata-bearing step carries the primary key, so the strategy 
still has to fold
+     * T.key/T.value into addV(). Otherwise the vertex creation fails first 
with an
+     * IllegalArgumentException about the missing primary key, and the 
traversal never reaches the
+     * metadata validation this test is about.
+     */
+    @Test
+    public void testPrimaryKeyIsFoldedIntoAddVBeforeMetaPropertyCheck() {
+        this.initSchema();
+
+        GraphTraversal<Vertex, Vertex> traversal = 
graph().traversal().addV(LABEL)
+                                                          .property("name", 
"marko", "since", "2024");
+
+        this.assertRejectedWhenExecuted(traversal, META_PROPERTIES_ERROR);
+    }
+
+    @Test
+    public void testUserSuppliedVertexPropertyIdRejected() {
+        this.initSchema();
+
+        GraphTraversal<Vertex, Vertex> traversal = 
graph().traversal().addV(LABEL)
+                                                          .property("name", 
"marko", T.id, 1);
+
+        this.assertRejectedWhenExecuted(traversal, USER_SUPPLIED_IDS_ERROR);
+    }
+
+    /**
+     * TinkerPop applies strategies to coalesce() children before a branch is 
selected, so eager
+     * validation would fail this traversal even though unfold() answers it 
and the addV() fallback
+     * never runs.
+     */
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testUnreachableCoalesceBranchIsNotValidated() {
+        this.initSchema();
+        graph().addVertex(T.label, LABEL, "name", "marko");
+        commitTx();
+
+        GraphTraversal<Vertex, Vertex> traversal =
+                graph().traversal().V().hasLabel(LABEL).fold()
+                       .coalesce(__.unfold(),
+                                 __.addV(LABEL).property("name", "marko", 
"since", "2024"));
+
+        this.assertFallbackNotExecuted(traversal);
+    }
+
+    @Test
+    public void testUnreachableChooseBranchIsNotValidated() {
+        this.initSchema();
+        graph().addVertex(T.label, LABEL, "name", "marko");
+        commitTx();
+
+        GraphTraversal<Vertex, Vertex> traversal =
+                graph().traversal().V().hasLabel(LABEL).fold()
+                       .choose(__.unfold().count().is(0L),
+                               __.addV(LABEL).property("name", "marko", 
"since", "2024"),
+                               __.unfold());
+
+        this.assertFallbackNotExecuted(traversal);
+    }
+
+    @Test
+    public void testFoldsSingleCardinalityProperties() {
+        this.initSchema();
+
+        GraphTraversal<Vertex, Vertex> traversal =
+                graph().traversal().addV(LABEL)
+                       .property(VertexProperty.Cardinality.single, "name", 
"marko")
+                       .property(VertexProperty.Cardinality.single, "country", 
"cn");
+        traversal.asAdmin().applyStrategies();
+        Assert.assertFalse(traversal.asAdmin().getSteps().stream()
+                                    
.anyMatch(AddPropertyStep.class::isInstance));
+
+        Vertex vertex = traversal.next();
+        commitTx();
+
+        Assert.assertEquals("marko", vertex.value("name"));
+        Assert.assertEquals("cn", vertex.value("country"));
+        Assert.assertEquals(1L, 
graph().traversal().V().hasLabel(LABEL).count().next());
+    }
+
+    private void assertRejectedWhenExecuted(GraphTraversal<?, Vertex> 
traversal, String expectedError) {
+        Assert.assertThrows(UnsupportedOperationException.class, 
traversal::next,
+                            e -> Assert.assertEquals(expectedError, 
e.getMessage()));
+
+        // The addV() step already ran, so only the rollback guarantees that 
nothing survives
+        graph().tx().rollback();
+        Assert.assertEquals(0L, 
graph().traversal().V().hasLabel(LABEL).count().next());
+    }
+
+    private void assertFallbackNotExecuted(GraphTraversal<?, Vertex> 
traversal) {
+        Vertex vertex = traversal.next();
+        commitTx();
+
+        Assert.assertEquals("marko", vertex.value("name"));
+        Assert.assertFalse(vertex.property("since").isPresent());
+        Assert.assertEquals(1L, 
graph().traversal().V().hasLabel(LABEL).count().next());
+    }
+}

Reply via email to