diff --git a/api/src/main/java/org/apache/iceberg/view/ViewHistoryEntry.java b/api/src/main/java/org/apache/iceberg/view/ViewHistoryEntry.java index 2840560655ed..351a27ce81c5 100644 --- a/api/src/main/java/org/apache/iceberg/view/ViewHistoryEntry.java +++ b/api/src/main/java/org/apache/iceberg/view/ViewHistoryEntry.java @@ -18,12 +18,15 @@ */ package org.apache.iceberg.view; +import org.immutables.value.Value; + /** * View history entry. * *
An entry contains a change to the view state. At the given timestamp, the current version was * set to the given version ID. */ +@Value.Immutable public interface ViewHistoryEntry { /** Return the timestamp in milliseconds of the change */ long timestampMillis(); diff --git a/core/src/main/java/org/apache/iceberg/view/ViewHistoryEntryParser.java b/core/src/main/java/org/apache/iceberg/view/ViewHistoryEntryParser.java new file mode 100644 index 000000000000..3debe1d61a32 --- /dev/null +++ b/core/src/main/java/org/apache/iceberg/view/ViewHistoryEntryParser.java @@ -0,0 +1,59 @@ +/* + * 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.iceberg.view; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import org.apache.iceberg.relocated.com.google.common.base.Preconditions; +import org.apache.iceberg.util.JsonUtil; + +class ViewHistoryEntryParser { + + private ViewHistoryEntryParser() {} + + static final String VERSION_ID = "version-id"; + static final String TIMESTAMP_MS = "timestamp-ms"; + + static String toJson(ViewHistoryEntry entry) { + return JsonUtil.generate(gen -> toJson(entry, gen), false); + } + + static void toJson(ViewHistoryEntry entry, JsonGenerator generator) throws IOException { + Preconditions.checkArgument(entry != null, "Invalid view history entry: null"); + generator.writeStartObject(); + generator.writeNumberField(TIMESTAMP_MS, entry.timestampMillis()); + generator.writeNumberField(VERSION_ID, entry.versionId()); + generator.writeEndObject(); + } + + static ViewHistoryEntry fromJson(String json) { + return JsonUtil.parse(json, ViewHistoryEntryParser::fromJson); + } + + static ViewHistoryEntry fromJson(JsonNode node) { + Preconditions.checkArgument(node != null, "Cannot parse view history entry from null object"); + Preconditions.checkArgument( + node.isObject(), "Cannot parse view history entry from non-object: %s", node); + return ImmutableViewHistoryEntry.builder() + .versionId(JsonUtil.getInt(VERSION_ID, node)) + .timestampMillis(JsonUtil.getLong(TIMESTAMP_MS, node)) + .build(); + } +} diff --git a/core/src/test/java/org/apache/iceberg/view/TestViewHistoryEntryParser.java b/core/src/test/java/org/apache/iceberg/view/TestViewHistoryEntryParser.java new file mode 100644 index 000000000000..1e2c33e44b00 --- /dev/null +++ b/core/src/test/java/org/apache/iceberg/view/TestViewHistoryEntryParser.java @@ -0,0 +1,76 @@ +/* + * 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.iceberg.view; + +import com.fasterxml.jackson.databind.JsonNode; +import org.assertj.core.api.Assertions; +import org.junit.Assert; +import org.junit.Test; + +public class TestViewHistoryEntryParser { + + @Test + public void testViewHistoryEntryFromJson() { + String json = "{\"timestamp-ms\":123,\"version-id\":1}"; + ViewHistoryEntry viewHistoryEntry = + ImmutableViewHistoryEntry.builder().versionId(1).timestampMillis(123).build(); + Assert.assertEquals( + "Should be able to deserialize valid view history entry", + viewHistoryEntry, + ViewHistoryEntryParser.fromJson(json)); + } + + @Test + public void testViewHistoryEntryToJson() { + String json = "{\"timestamp-ms\":123,\"version-id\":1}"; + ViewHistoryEntry viewHistoryEntry = + ImmutableViewHistoryEntry.builder().versionId(1).timestampMillis(123).build(); + Assert.assertEquals( + "Should be able to serialize view history entry", + json, + ViewHistoryEntryParser.toJson(viewHistoryEntry)); + } + + @Test + public void testNullViewHistoryEntry() { + Assertions.assertThatThrownBy(() -> ViewHistoryEntryParser.fromJson((JsonNode) null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse view history entry from null object"); + + Assertions.assertThatThrownBy(() -> ViewHistoryEntryParser.toJson(null)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Invalid view history entry: null"); + } + + @Test + public void testViewHistoryEntryMissingFields() { + Assertions.assertThatThrownBy(() -> ViewHistoryEntryParser.fromJson("{}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing int: version-id"); + + Assertions.assertThatThrownBy( + () -> ViewHistoryEntryParser.fromJson("{\"timestamp-ms\":\"123\"}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing int: version-id"); + + Assertions.assertThatThrownBy(() -> ViewHistoryEntryParser.fromJson("{\"version-id\":1}")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot parse missing long: timestamp-ms"); + } +}