fix(server): keep schema ~create_time userdata as Date after reload#3026
fix(server): keep schema ~create_time userdata as Date after reload#3026dpol1 wants to merge 1 commit into
Conversation
Normalize server-side schema ~create_time userdata in SchemaElement so serializer reloads and fromMap paths keep the Date contract. Add SchemaElement, TextSerializer, and BinarySerializer coverage. Closes apache#3013
There was a problem hiding this comment.
Pull request overview
This PR fixes a schema reload regression where internal schema userdata Userdata.CREATE_TIME ("~create_time") can round-trip through backend JSON serializers as a String and come back as String after reload, violating the server-side contract that it should be a java.util.Date.
Changes:
- Added
SchemaElement.normalizeUserdataValue(key, value)to re-type~create_timestring values back intoDate(and throw a clearer error on invalid values). - Routed both
SchemaElement.userdata(String, Object)andSchemaElement.userdata(Userdata)through the normalization logic (and added a null-argument check for the bulk setter). - Added regression tests for normalization behavior and for Text/Binary serializer round-trips, and registered them in
UnitTestSuite.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/schema/SchemaElement.java | Normalizes ~create_time userdata on set (single and bulk), ensuring reload paths restore Date instead of String. |
| hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/core/SchemaElementTest.java | Unit coverage for normalization behavior, idempotency, invalid strings, and null bulk userdata validation. |
| hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/TextSerializerTest.java | Regression test verifying TextSerializer schema round-trip keeps CREATE_TIME as Date. |
| hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/serializer/BinarySerializerTest.java | Regression test verifying BinarySerializer schema round-trip keeps CREATE_TIME as Date. |
| hugegraph-server/hugegraph-test/src/main/java/org/apache/hugegraph/unit/UnitTestSuite.java | Registers the new unit tests in the suite. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
imbajin
left a comment
There was a problem hiding this comment.
Reviewed correctness, design, and tests. No blocking issues — fix is correct and narrowly scoped, all backend reload paths (Text / Binary / Mysql / Cassandra / HBase / Postgres / Palo / Hstore + the four fromMap paths) route through the new helper, and DateUtil.parse is symmetric with the "yyyy-MM-dd HH:mm:ss.SSS" format used by JsonUtil/HugeGraphSONModule. Inline comments cover important refinements (helper placement, public setter docs, test depth). A few additional minor notes below that don't have a clean inline target:
- 🧹
removeUserdata(Userdata)(SchemaElement.java:114-118) lacks a null check — addingE.checkArgumentNotNull(userdata, ...)keeps the bulk setters symmetric with the new check onuserdata(Userdata). - 🧹 Behavior change worth a release note: any REST client that posts
~create_timeas a JSON string in user-data now silently receives aDate(or anIllegalArgumentExceptionif malformed).~create_timeis a server-reserved key (~prefix, written bySchemaTransaction.setCreateTimeIfNeeded), so risk is small, but flagging it in the PR body / changelog is helpful. - 🧹 Follow-up tracking:
Userdata.DEFAULT_VALUEhas the same Jackson type-erasure problem (e.g., a property key whose data type isDatewill reload its default value asString). The PR description already defers thehugegraph-structmirror — please addDEFAULT_VALUEto the same follow-up list, since the root cause and fix shape are identical.
| * a raw {@code Map}. This method restores the original type after | ||
| * deserialization. Idempotent for values already of the expected type. | ||
| */ | ||
| private static Object normalizeUserdataValue(String key, Object value) { |
There was a problem hiding this comment.
normalizeUserdataValue onto Userdata itself (next to CREATE_TIME / DEFAULT_VALUE constants and the existing check() validator). Three benefits:
- Cohesion — the rule is about userdata semantics, not about
SchemaElement. - Reuse for the announced follow-up — the PR notes that
hugegraph-structhas a mirrorUserdatawith the same shape; if normalization lives onUserdata, the struct fix becomes a one-line delegation. - Future userdata readers — anything that hydrates a
Userdatafrom raw JSON gets the fix for free, not just the twoSchemaElementsetters.
Not blocking — the current placement works. But shifting it would prevent re-implementing this for every userdata-bearing class.
| @@ -99,11 +100,37 @@ public Map<String, Object> userdata() { | |||
| public void userdata(String key, Object value) { | |||
There was a problem hiding this comment.
userdata(...) setters now silently coerce String → Date for ~create_time and throw IllegalArgumentException on a malformed string. The detailed Javadoc lives on the private helper, where IDE tooltips / public-API readers won't see it.
A one-line note on each public setter (or on the class header) calling out the implicit conversion would prevent surprise for callers reading the public API.
|
|
||
| @Test | ||
| public void testSingleSetterLeavesOtherStringKeysUntouched() { | ||
| SchemaElement schema = newSchema(); |
There was a problem hiding this comment.
DateUtil.parse(...)'s RuntimeException as the cause of the thrown IllegalArgumentException. Worth locking that contract in:
| SchemaElement schema = newSchema(); | |
| }, e -> { | |
| Assert.assertContains(Userdata.CREATE_TIME, e.getMessage()); | |
| Assert.assertContains("not-a-date", e.getMessage()); | |
| Assert.assertNotNull(e.getCause()); | |
| }); |
Protects against a future refactor that drops the cause chain (which would silently degrade error diagnostics).
| }, e -> { | ||
| Assert.assertContains("userdata", e.getMessage()); | ||
| }); | ||
| } |
There was a problem hiding this comment.
schema.userdata(Userdata.CREATE_TIME, null) still throws IllegalArgumentException from the existing E.checkArgumentNotNull(value, ...).
Reason: the new helper sits between the null check and the put. A future refactor that reorders these (e.g., parse first, null-check second) would silently break the null contract — currently nothing pins it down for the CREATE_TIME key specifically.
| Assert.assertTrue("CREATE_TIME should be a Date after round-trip, " + | ||
| "was " + (value == null ? "null" : value.getClass()), | ||
| value instanceof Date); | ||
| Assert.assertEquals(created, value); |
There was a problem hiding this comment.
- Only
PropertyKeyis round-tripped.VertexLabel,EdgeLabel, andIndexLabelreach the helper through the bulk setter (fromMap→userdata(new Userdata(...))), which is a different control path thanreadPropertyKey's per-entry single setter. Adding even one assertion for any of those three would harden coverage cheaply. - Near-duplicate test classes. This file and the new
BinarySerializerTest#testPropertyKeyUserdataCreateTimeRoundTripsAsDatediffer only in serializer type. A@Parameterizedtest or a shared helper inBaseUnitTesttakingFunction<HugeConfig, AbstractSerializer>would remove ~50 lines and stop the two from drifting.
Neither blocks merge; (1) is the more valuable of the two.
close [Bug] schema ~create_time userdata round-trips Date as String after cache reload #3013
Userdata.CREATE_TIME("~create_time") is written asjava.util.Dateby
SchemaTransaction.setCreateTimeIfNeeded(), then persisted through backendserializers as JSON. On reload, userdata is deserialized through raw
Map.classpaths, so Jackson cannot restore the value type andschemaElement.userdata().get(Userdata.CREATE_TIME)can come back asString.That breaks callers and existing tests that expect
~create_timeto keep theserver-side
Datecontract after a schema is reloaded from backend storage.Main Changes
Added a single
SchemaElement.normalizeUserdataValue(key, value)helper.Userdata.CREATE_TIMEstring values back withDateUtil.parse(...).Datevalues and all other userdata keys unchanged.IllegalArgumentExceptionif a~create_timestring is invalid.Routed both userdata setters through the helper:
userdata(String, Object)covers serializer reload paths such asTextSerializer,BinarySerializer,MysqlSerializer, andCassandraSerializer.userdata(Userdata)covers schemafromMap()hydration paths forPropertyKey,VertexLabel,EdgeLabel, andIndexLabel.Added a null check to
userdata(Userdata)for consistent argument validation.Added regression coverage for schema userdata normalization and serializer
round trips.
This PR is intentionally limited to the server-side
SchemaElement. The mirrorclass in
hugegraph-structhas a similar userdata shape and can be handled in aseparate follow-up PR.
Verifying these changes
SchemaElementTestcovers single-key normalization, bulk normalization,idempotency for
Date, invalid~create_timestrings, null bulk userdata,and non-
CREATE_TIMEkeys.TextSerializerTest#testPropertyKeyUserdataCreateTimeRoundTripsAsDateverifies the text serializer schema round trip keeps
CREATE_TIMEasDate.BinarySerializerTest#testPropertyKeyUserdataCreateTimeRoundTripsAsDateverifies the binary serializer schema round trip keeps
CREATE_TIMEasDate.UnitTestSuite.Does this PR potentially affect the following parts?
The public SchemaElement.userdata(...) signatures are unchanged. The behavior
change is limited to internal userdata normalization for Userdata.CREATE_TIME.
Documentation Status
Notes
Userdata.CREATE_TIME(~create_time) only.~create_timeis a server-reserved userdata key; valid string values are normalized back tojava.util.Date, while malformed values now fail withIllegalArgumentException.Userdata.DEFAULT_VALUE(~default_value) reloads and checking the same raw userdata deserialization gap inhugegraph-struct.