diff --git a/CHANGES.txt b/CHANGES.txt index b4a5206aa326..23a694b0bd0d 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,5 @@ 5.1 + * Make JsonUtils serialize Instant always with the same format (CASSANDRA-20209) * Port Harry v2 to trunk (CASSANDRA-20200) * Enable filtering of snapshots on keyspace, table and snapshot name in nodetool listsnapshots (CASSANDRA-20151) * Create manifest upon loading where it does not exist or enrich it (CASSANDRA-20150) diff --git a/src/java/org/apache/cassandra/utils/JsonUtils.java b/src/java/org/apache/cassandra/utils/JsonUtils.java index aa0963bf5859..4868fedd4598 100644 --- a/src/java/org/apache/cassandra/utils/JsonUtils.java +++ b/src/java/org/apache/cassandra/utils/JsonUtils.java @@ -21,6 +21,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.time.Instant; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -29,8 +32,8 @@ import com.fasterxml.jackson.core.util.BufferRecyclers; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; -import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.ser.InstantSerializer; import org.apache.cassandra.io.util.File; import org.apache.cassandra.io.util.FileInputStreamPlus; import org.apache.cassandra.io.util.FileOutputStreamPlus; @@ -44,10 +47,20 @@ public final class JsonUtils public static final ObjectMapper JSON_OBJECT_MAPPER = new ObjectMapper(new JsonFactory()); // checkstyle: permit this instantiation public static final ObjectWriter JSON_OBJECT_PRETTY_WRITER; + private static class GlobalInstantSerializer extends InstantSerializer + { + private GlobalInstantSerializer() + { + super(InstantSerializer.INSTANCE, + false, + false, + DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'").withZone(ZoneOffset.UTC)); + } + } + static { - JSON_OBJECT_MAPPER.registerModule(new JavaTimeModule()); - JSON_OBJECT_MAPPER.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + JSON_OBJECT_MAPPER.registerModule(new JavaTimeModule().addSerializer(Instant.class, new GlobalInstantSerializer())); JSON_OBJECT_PRETTY_WRITER = JSON_OBJECT_MAPPER.writerWithDefaultPrettyPrinter(); } diff --git a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java index 7f5b28c7596d..0febbe660cfa 100644 --- a/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java +++ b/test/unit/org/apache/cassandra/db/ColumnFamilyStoreTest.java @@ -449,9 +449,8 @@ private void testTrueSnapshotSizeInternal(String keyspace, String table, boolean long anotherWithIndexSize = listedSnapshots.get("snapshot2_with_index").computeSizeOnDiskBytes(); long anotherWithIndexTrueSize = listedSnapshots.get("snapshot2_with_index").computeTrueSizeBytes(); - // TODO CASSANDRA-20209 - assertTrue(withIndexSize == anotherWithIndexSize || (withIndexSize + 4 == anotherWithIndexSize) || (withIndexSize - 4 == anotherWithIndexSize)); - assertTrue(withIndexTrueSize == anotherWithIndexTrueSize || (withIndexTrueSize + 4 == anotherWithIndexTrueSize) || (withIndexTrueSize - 4 == anotherWithIndexTrueSize)); + assertEquals(withIndexSize, anotherWithIndexSize); + assertEquals(withIndexTrueSize, anotherWithIndexTrueSize); } private void rebuildIndices(ColumnFamilyStore cfs) diff --git a/test/unit/org/apache/cassandra/utils/JsonUtilsTest.java b/test/unit/org/apache/cassandra/utils/JsonUtilsTest.java new file mode 100644 index 000000000000..1bd32079f8f4 --- /dev/null +++ b/test/unit/org/apache/cassandra/utils/JsonUtilsTest.java @@ -0,0 +1,36 @@ +/* + * 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.cassandra.utils; + +import org.junit.Test; + +import static java.time.Instant.parse; +import static org.apache.cassandra.utils.JsonUtils.writeAsJsonString; +import static org.junit.Assert.assertEquals; + +public class JsonUtilsTest +{ + @Test + public void testTimestampSerialisation() + { + assertEquals("\"2025-01-15T13:26:45.040Z\"", writeAsJsonString(parse("2025-01-15T13:26:45.04Z"))); + assertEquals("\"2025-01-15T13:26:45.100Z\"", writeAsJsonString(parse("2025-01-15T13:26:45.1Z"))); + assertEquals("\"2025-01-15T13:26:45.123Z\"", writeAsJsonString(parse("2025-01-15T13:26:45.123Z"))); + } +}