From fbed96269799e02d9e8245eeb677676f59872095 Mon Sep 17 00:00:00 2001 From: Davide Polato Date: Thu, 21 May 2026 15:29:56 +0200 Subject: [PATCH] fix(server): preserve typed DEFAULT_VALUE after JSON reload 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. Fixes #3028 --- .../apache/hugegraph/schema/PropertyKey.java | 7 ++- .../apache/hugegraph/core/VertexCoreTest.java | 28 +++++++++ .../unit/core/SchemaElementTest.java | 61 +++++++++++++++++++ .../unit/serializer/BinarySerializerTest.java | 22 +++++++ .../unit/serializer/TextSerializerTest.java | 22 +++++++ .../hugegraph/struct/schema/PropertyKey.java | 7 ++- .../struct/schema/PropertyKeyTest.java | 47 ++++++++++++++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 hugegraph-struct/src/test/java/org/apache/hugegraph/struct/schema/PropertyKeyTest.java 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 64d5115d80..ecfdb7b3ef 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,12 @@ public void defineDefaultValue(Object value) { public Object defaultValue() { // TODO add a field default_value - return this.userdata().get(Userdata.DEFAULT_VALUE); + Object value = this.userdata().get(Userdata.DEFAULT_VALUE); + // 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. + return value == null ? null : this.validValue(value); } 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 c56db9f2b9..285b3c026d 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 @@ -69,6 +69,7 @@ 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 +827,33 @@ public void testAddVertexWithDefaultPropertyValue() { 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 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 0bcdddf89a..9853af107e 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.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 void testVertexLabelFromMapNormalizesCreateTimeString() { createTime); } + @Test + public void testPropertyKeyFromMapNormalizesDateDefaultValue() { + String formatted = "2026-05-14 10:11:12.345"; + Map userdata = new HashMap<>(); + userdata.put(Userdata.DEFAULT_VALUE, formatted); + + Map 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 userdata = new HashMap<>(); + userdata.put(Userdata.DEFAULT_VALUE, Arrays.asList(first, second)); + + Map 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 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 ba5136923d..5fdaa9fda4 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 @@ -29,6 +29,7 @@ import org.apache.hugegraph.structure.HugeVertex; import org.apache.hugegraph.testutil.Assert; import org.apache.hugegraph.testutil.Whitebox; +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 +133,27 @@ public void testPropertyKeyUserdataCreateTimeRoundTripsAsDate() { 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 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 97df554c43..8181303991 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 @@ -26,6 +26,7 @@ import org.apache.hugegraph.schema.PropertyKey; import org.apache.hugegraph.schema.Userdata; import org.apache.hugegraph.testutil.Assert; +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 +54,25 @@ public void testPropertyKeyUserdataCreateTimeRoundTripsAsDate() { 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); + } } 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 eaf02db04b..c7ac18760c 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,12 @@ public void defineDefaultValue(Object value) { public Object defaultValue() { // TODO add a field default_value - return this.userdata().get(Userdata.DEFAULT_VALUE); + Object value = this.userdata().get(Userdata.DEFAULT_VALUE); + // 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. + return value == null ? null : this.validValue(value); } 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 0000000000..620588ac51 --- /dev/null +++ b/hugegraph-struct/src/test/java/org/apache/hugegraph/struct/schema/PropertyKeyTest.java @@ -0,0 +1,47 @@ +/* + * 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.Date; + +import org.apache.hugegraph.id.IdGenerator; +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); + } +}