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)));
+ }
+}