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..5bf34ea530 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 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); + 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 c56db9f2b9..d33f9bb07d 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 @@ 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.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 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 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 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..02d64bcc78 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.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 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 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 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 97df554c43..4d55a9b046 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.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 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); + } + + @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 eaf02db04b..81dae36697 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 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); + 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 0000000000..d8441144f2 --- /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))); + } +}