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 340583280 fix(server): normalize typed DEFAULT_VALUE after JSON reload 
(#3035)
340583280 is described below

commit 34058328005502b19bce4f541df510da344b3f43
Author: Davide Polato <[email protected]>
AuthorDate: Wed Jun 3 09:31:26 2026 -0400

    fix(server): normalize typed DEFAULT_VALUE after JSON reload (#3035)
    
    Normalizes PropertyKey default values to their declared data type upon 
retrieval. Previously, values stored in userdata could lose their original type 
during serialization and deserialization (e.g., Date becoming String), leading 
to type mismatches.
    
    The `defaultValue()` method now converts deserialized string 
representations back to their expected runtime types. This change is verified 
with extensive tests covering schema parsing, vertex property assignment, and 
both binary and text serializers.
---
 .../org/apache/hugegraph/schema/PropertyKey.java   | 17 +++++-
 .../org/apache/hugegraph/core/VertexCoreTest.java  | 59 ++++++++++++++++++
 .../hugegraph/unit/core/SchemaElementTest.java     | 61 +++++++++++++++++++
 .../unit/serializer/BinarySerializerTest.java      | 52 ++++++++++++++++
 .../unit/serializer/TextSerializerTest.java        | 51 ++++++++++++++++
 .../hugegraph/struct/schema/PropertyKey.java       | 17 +++++-
 .../hugegraph/struct/schema/PropertyKeyTest.java   | 70 ++++++++++++++++++++++
 7 files changed, 325 insertions(+), 2 deletions(-)

diff --git 
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/PropertyKey.java
 
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/PropertyKey.java
index 64d5115d8..5bf34ea53 100644
--- 
a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/PropertyKey.java
+++ 
b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/PropertyKey.java
@@ -121,7 +121,22 @@ public class PropertyKey extends SchemaElement implements 
Propertiable {
 
     public Object defaultValue() {
         // TODO add a field default_value
-        return this.userdata().get(Userdata.DEFAULT_VALUE);
+        Object value = this.userdata().get(Userdata.DEFAULT_VALUE);
+        if (value == null) {
+            return null;
+        }
+
+        // Userdata is reloaded from JSON as a raw Map, so a typed default
+        // value (e.g. Date) comes back as a String. Normalize it to the
+        // runtime type expected by this property key's data type. Idempotent
+        // for values already of the expected type.
+        Object raw = value;
+        if (this.cardinality == Cardinality.SET && value instanceof Collection 
&&
+            !(value instanceof Set)) {
+            raw = new LinkedHashSet<>((Collection<?>) value);
+        }
+
+        return this.validValueOrThrow(raw);
     }
 
     public boolean hasSameContent(PropertyKey other) {
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
index c56db9f2b..d33f9bb07 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/core/VertexCoreTest.java
@@ -19,6 +19,7 @@ package org.apache.hugegraph.core;
 
 import java.math.BigDecimal;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
@@ -69,6 +70,7 @@ import org.apache.hugegraph.type.define.HugeKeys;
 import org.apache.hugegraph.type.define.WriteType;
 import org.apache.hugegraph.util.Blob;
 import org.apache.hugegraph.util.CollectionUtil;
+import org.apache.hugegraph.util.DateUtil;
 import org.apache.hugegraph.util.LongEncoding;
 import org.apache.tinkerpop.gremlin.process.traversal.P;
 import org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversal;
@@ -826,6 +828,63 @@ public class VertexCoreTest extends BaseCoreTest {
         Assert.assertFalse(vertex.values("age").hasNext());
     }
 
+    @Test
+    public void testAddVertexWithDateDefaultValue() {
+        SchemaManager schema = graph().schema();
+
+        Date joined = DateUtil.parse("2026-05-14 10:11:12.345");
+        schema.propertyKey("joinDate").asDate()
+              .userdata(Userdata.DEFAULT_VALUE, joined).create();
+        schema.vertexLabel("person")
+              .properties("joinDate")
+              .nullableKeys("joinDate").append();
+
+        // No 'joinDate' supplied
+        Vertex vertex = graph().addVertex(T.label, "person",
+                                          "name", "Baby", "city", "Shanghai");
+
+        this.commitTx();
+
+        // Reload from backend then query: the typed default must survive the
+        // JSON serialize/reload round-trip as a Date, not a String (#3028).
+        vertex = graph().vertex(vertex.id());
+        Object value = vertex.value("joinDate");
+        Assert.assertTrue("default 'joinDate' should be a Date, was " +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Date);
+        Assert.assertEquals(joined, value);
+    }
+
+    @Test
+    public void testAddVertexWithDateSetDefaultValue() {
+        SchemaManager schema = graph().schema();
+
+        String dateStr = "2026-05-14 10:11:12.345";
+        Date expected = DateUtil.parse(dateStr);
+
+        // Simulate JSON-deserialized default: ArrayList of Strings with 
duplicates
+        schema.propertyKey("joinDates").asDate().valueSet()
+              .userdata(Userdata.DEFAULT_VALUE, Arrays.asList(dateStr, 
dateStr))
+              .create();
+        schema.vertexLabel("person")
+              .properties("joinDates")
+              .nullableKeys("joinDates").append();
+
+        Vertex vertex = graph().addVertex(T.label, "person",
+                                          "name", "Baby", "city", "Shanghai");
+        this.commitTx();
+
+        vertex = graph().vertex(vertex.id());
+        Object raw = vertex.value("joinDates");
+
+        Assert.assertTrue("joinDates should be a Set, was " +
+                          (raw == null ? "null" : raw.getClass()),
+                          raw instanceof Set);
+        Set<?> values = (Set<?>) raw;
+        Assert.assertEquals("duplicates must be collapsed", 1, values.size());
+        Assert.assertTrue(values.contains(expected));
+    }
+
     @Test
     public void testAddVertexWithNotExistsVertexPropKey() {
         HugeGraph graph = graph();
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/SchemaElementTest.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/SchemaElementTest.java
index 0bcdddf89..9853af107 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/SchemaElementTest.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/SchemaElementTest.java
@@ -17,8 +17,11 @@
 
 package org.apache.hugegraph.unit.core;
 
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 import org.apache.hugegraph.backend.id.IdGenerator;
@@ -27,6 +30,8 @@ import org.apache.hugegraph.schema.SchemaElement;
 import org.apache.hugegraph.schema.Userdata;
 import org.apache.hugegraph.schema.VertexLabel;
 import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.type.define.Cardinality;
+import org.apache.hugegraph.type.define.DataType;
 import org.apache.hugegraph.unit.FakeObjects;
 import org.apache.hugegraph.util.DateUtil;
 import org.junit.Test;
@@ -199,6 +204,62 @@ public class SchemaElementTest {
                             createTime);
     }
 
+    @Test
+    public void testPropertyKeyFromMapNormalizesDateDefaultValue() {
+        String formatted = "2026-05-14 10:11:12.345";
+        Map<String, Object> userdata = new HashMap<>();
+        userdata.put(Userdata.DEFAULT_VALUE, formatted);
+
+        Map<String, Object> map = new HashMap<>();
+        map.put(PropertyKey.P.ID, 1);
+        map.put(PropertyKey.P.NAME, "birth");
+        map.put(PropertyKey.P.DATA_TYPE, DataType.DATE.string());
+        map.put(PropertyKey.P.CARDINALITY, Cardinality.SINGLE.string());
+        map.put(PropertyKey.P.USERDATA, userdata);
+
+        PropertyKey propertyKey = PropertyKey.fromMap(map,
+                                                      new 
FakeObjects().graph());
+
+        Object value = propertyKey.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Date, was " +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Date);
+        Assert.assertEquals(DateUtil.parse(formatted), value);
+    }
+
+    @Test
+    public void testPropertyKeyFromMapNormalizesDateSetDefaultValue() {
+        String first = "2026-05-14 10:11:12.345";
+        String second = "2026-05-15 11:12:13.456";
+        Map<String, Object> userdata = new HashMap<>();
+        userdata.put(Userdata.DEFAULT_VALUE, Arrays.asList(first, second));
+
+        Map<String, Object> map = new HashMap<>();
+        map.put(PropertyKey.P.ID, 1);
+        map.put(PropertyKey.P.NAME, "tags");
+        map.put(PropertyKey.P.DATA_TYPE, DataType.DATE.string());
+        map.put(PropertyKey.P.CARDINALITY, Cardinality.SET.string());
+        map.put(PropertyKey.P.USERDATA, userdata);
+
+        PropertyKey propertyKey = PropertyKey.fromMap(map,
+                                                      new 
FakeObjects().graph());
+
+        Object value = propertyKey.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Collection, was " +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Collection);
+        Collection<?> values = (Collection<?>) value;
+        Assert.assertEquals(2, values.size());
+        for (Object element : values) {
+            Assert.assertTrue("each element should be a Date, was " +
+                              (element == null ? "null" : element.getClass()),
+                              element instanceof Date);
+        }
+        List<Date> expected = Arrays.asList(DateUtil.parse(first),
+                                            DateUtil.parse(second));
+        Assert.assertTrue(values.containsAll(expected));
+    }
+
     @Test
     public void testBulkSetterRejectsNullUserdata() {
         SchemaElement schema = newSchema();
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/BinarySerializerTest.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/BinarySerializerTest.java
index ba5136923..02d64bcc7 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/BinarySerializerTest.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/BinarySerializerTest.java
@@ -17,7 +17,9 @@
 
 package org.apache.hugegraph.unit.serializer;
 
+import java.util.Arrays;
 import java.util.Date;
+import java.util.Set;
 
 import org.apache.hugegraph.backend.id.IdGenerator;
 import org.apache.hugegraph.backend.serializer.BinarySerializer;
@@ -29,6 +31,8 @@ import org.apache.hugegraph.structure.HugeEdge;
 import org.apache.hugegraph.structure.HugeVertex;
 import org.apache.hugegraph.testutil.Assert;
 import org.apache.hugegraph.testutil.Whitebox;
+import org.apache.hugegraph.type.define.Cardinality;
+import org.apache.hugegraph.type.define.DataType;
 import org.apache.hugegraph.unit.BaseUnitTest;
 import org.apache.hugegraph.unit.FakeObjects;
 import org.apache.hugegraph.util.DateUtil;
@@ -132,6 +136,54 @@ public class BinarySerializerTest extends BaseUnitTest {
         Assert.assertEquals(created, value);
     }
 
+    @Test
+    public void testPropertyKeyDefaultValueRoundTripsAsDate() {
+        HugeConfig config = FakeObjects.newConfig();
+        BinarySerializer ser = new BinarySerializer(config);
+
+        FakeObjects objects = new FakeObjects();
+        PropertyKey original = objects.newPropertyKey(IdGenerator.of(1L),
+                                                      "name", DataType.DATE);
+        Date defaultValue = DateUtil.parse("2026-05-14 10:11:12.345");
+        original.userdata(Userdata.DEFAULT_VALUE, defaultValue);
+
+        BackendEntry entry = ser.writePropertyKey(original);
+        PropertyKey reloaded = ser.readPropertyKey(objects.graph(), entry);
+
+        Object value = reloaded.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Date after round-trip, " +
+                          "was " + (value == null ? "null" : value.getClass()),
+                          value instanceof Date);
+        Assert.assertEquals(defaultValue, value);
+    }
+
+    @Test
+    public void testPropertyKeySetDefaultValueRoundTripsAsDate() {
+        HugeConfig config = FakeObjects.newConfig();
+        BinarySerializer ser = new BinarySerializer(config);
+
+        FakeObjects objects = new FakeObjects();
+        PropertyKey original = objects.newPropertyKey(IdGenerator.of(2L),
+                                                      "tags", DataType.DATE);
+        original.cardinality(Cardinality.SET);
+
+        String dateStr = "2026-05-14 10:11:12.345";
+        Date expected = DateUtil.parse(dateStr);
+        // ArrayList<String> with duplicates — what JSON deserialization 
produces
+        original.userdata(Userdata.DEFAULT_VALUE, Arrays.asList(dateStr, 
dateStr));
+
+        BackendEntry entry = ser.writePropertyKey(original);
+        PropertyKey reloaded = ser.readPropertyKey(objects.graph(), entry);
+
+        Object value = reloaded.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Set after round-trip, was 
" +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Set);
+        Set<?> values = (Set<?>) value;
+        Assert.assertEquals("duplicates must be collapsed", 1, values.size());
+        Assert.assertTrue(values.contains(expected));
+    }
+
     @Test
     public void testEdgeForPartition() {
         BinarySerializer ser = new BinarySerializer(true, true, true);
diff --git 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/TextSerializerTest.java
 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/TextSerializerTest.java
index 97df554c4..4d55a9b04 100644
--- 
a/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/TextSerializerTest.java
+++ 
b/hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/TextSerializerTest.java
@@ -17,7 +17,9 @@
 
 package org.apache.hugegraph.unit.serializer;
 
+import java.util.Arrays;
 import java.util.Date;
+import java.util.Set;
 
 import org.apache.hugegraph.backend.id.IdGenerator;
 import org.apache.hugegraph.backend.serializer.TextSerializer;
@@ -26,6 +28,8 @@ import org.apache.hugegraph.config.HugeConfig;
 import org.apache.hugegraph.schema.PropertyKey;
 import org.apache.hugegraph.schema.Userdata;
 import org.apache.hugegraph.testutil.Assert;
+import org.apache.hugegraph.type.define.Cardinality;
+import org.apache.hugegraph.type.define.DataType;
 import org.apache.hugegraph.unit.BaseUnitTest;
 import org.apache.hugegraph.unit.FakeObjects;
 import org.apache.hugegraph.util.DateUtil;
@@ -53,4 +57,51 @@ public class TextSerializerTest extends BaseUnitTest {
                           value instanceof Date);
         Assert.assertEquals(created, value);
     }
+
+    @Test
+    public void testPropertyKeyDefaultValueRoundTripsAsDate() {
+        HugeConfig config = FakeObjects.newConfig();
+        TextSerializer ser = new TextSerializer(config);
+
+        FakeObjects objects = new FakeObjects();
+        PropertyKey original = objects.newPropertyKey(IdGenerator.of(1L),
+                                                      "name", DataType.DATE);
+        Date defaultValue = DateUtil.parse("2026-05-14 10:11:12.345");
+        original.userdata(Userdata.DEFAULT_VALUE, defaultValue);
+
+        BackendEntry entry = ser.writePropertyKey(original);
+        PropertyKey reloaded = ser.readPropertyKey(objects.graph(), entry);
+
+        Object value = reloaded.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Date after round-trip, " +
+                          "was " + (value == null ? "null" : value.getClass()),
+                          value instanceof Date);
+        Assert.assertEquals(defaultValue, value);
+    }
+
+    @Test
+    public void testPropertyKeySetDefaultValueRoundTripsAsDate() {
+        HugeConfig config = FakeObjects.newConfig();
+        TextSerializer ser = new TextSerializer(config);
+
+        FakeObjects objects = new FakeObjects();
+        PropertyKey original = objects.newPropertyKey(IdGenerator.of(2L),
+                                                      "tags", DataType.DATE);
+        original.cardinality(Cardinality.SET);
+
+        String dateStr = "2026-05-14 10:11:12.345";
+        Date expected = DateUtil.parse(dateStr);
+        original.userdata(Userdata.DEFAULT_VALUE, Arrays.asList(dateStr, 
dateStr));
+
+        BackendEntry entry = ser.writePropertyKey(original);
+        PropertyKey reloaded = ser.readPropertyKey(objects.graph(), entry);
+
+        Object value = reloaded.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Set after round-trip, was 
" +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Set);
+        Set<?> values = (Set<?>) value;
+        Assert.assertEquals("duplicates must be collapsed", 1, values.size());
+        Assert.assertTrue(values.contains(expected));
+    }
 }
diff --git 
a/hugegraph-struct/src/main/java/org/apache/hugegraph/struct/schema/PropertyKey.java
 
b/hugegraph-struct/src/main/java/org/apache/hugegraph/struct/schema/PropertyKey.java
index eaf02db04..81dae3669 100644
--- 
a/hugegraph-struct/src/main/java/org/apache/hugegraph/struct/schema/PropertyKey.java
+++ 
b/hugegraph-struct/src/main/java/org/apache/hugegraph/struct/schema/PropertyKey.java
@@ -126,7 +126,22 @@ public class PropertyKey extends SchemaElement implements 
Propfiable {
 
     public Object defaultValue() {
         // TODO add a field default_value
-        return this.userdata().get(Userdata.DEFAULT_VALUE);
+        Object value = this.userdata().get(Userdata.DEFAULT_VALUE);
+        if (value == null) {
+            return null;
+        }
+
+        // Userdata is reloaded from JSON as a raw Map, so a typed default
+        // value (e.g. Date) comes back as a String. Normalize it to the
+        // runtime type expected by this property key's data type. Idempotent
+        // for values already of the expected type.
+        Object raw = value;
+        if (this.cardinality == Cardinality.SET && value instanceof Collection 
&&
+            !(value instanceof Set)) {
+            raw = new LinkedHashSet<>((Collection<?>) value);
+        }
+
+        return this.validValueOrThrow(raw);
     }
 
     public boolean hasSameContent(PropertyKey other) {
diff --git 
a/hugegraph-struct/src/test/java/org/apache/hugegraph/struct/schema/PropertyKeyTest.java
 
b/hugegraph-struct/src/test/java/org/apache/hugegraph/struct/schema/PropertyKeyTest.java
new file mode 100644
index 000000000..d8441144f
--- /dev/null
+++ 
b/hugegraph-struct/src/test/java/org/apache/hugegraph/struct/schema/PropertyKeyTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.struct.schema;
+
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Set;
+
+import org.apache.hugegraph.id.IdGenerator;
+import org.apache.hugegraph.type.define.Cardinality;
+import org.apache.hugegraph.type.define.DataType;
+import org.apache.hugegraph.util.DateUtil;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PropertyKeyTest {
+
+    @Test
+    public void testDefaultValueNormalizedToDate() {
+        // Userdata reloaded from JSON keeps ~default_value as a String;
+        // defaultValue() must normalize it to the data type's runtime type
+        // (#3028).
+        String formatted = "2026-05-14 10:11:12.345";
+        PropertyKey propertyKey = new PropertyKey(null, IdGenerator.of(1),
+                                                  "joinDate");
+        propertyKey.dataType(DataType.DATE);
+        propertyKey.userdata(Userdata.DEFAULT_VALUE, formatted);
+
+        Object value = propertyKey.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Date, was " +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Date);
+        Assert.assertEquals(DateUtil.parse(formatted), value);
+    }
+
+    @Test
+    public void testSetDefaultValueCollapsesDuplicatesAndReturnsSet() {
+        String formatted = "2026-05-14 10:11:12.345";
+        PropertyKey propertyKey = new PropertyKey(null, IdGenerator.of(1),
+                                                  "joinDate");
+        propertyKey.dataType(DataType.DATE);
+        propertyKey.cardinality(Cardinality.SET);
+        propertyKey.userdata(Userdata.DEFAULT_VALUE,
+                             Arrays.asList(formatted, formatted));
+
+        Object value = propertyKey.defaultValue();
+        Assert.assertTrue("DEFAULT_VALUE should be a Set, was " +
+                          (value == null ? "null" : value.getClass()),
+                          value instanceof Set);
+
+        Set<?> values = (Set<?>) value;
+        Assert.assertEquals(1, values.size());
+        Assert.assertTrue(values.contains(DateUtil.parse(formatted)));
+    }
+}

Reply via email to