You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by ao...@apache.org on 2022/10/18 19:30:19 UTC
[iceberg] branch master updated: Core: Fix NPE in SnapshotUtil when parent snapshot ID is null (#6005)
This is an automated email from the ASF dual-hosted git repository.
aokolnychyi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/iceberg.git
The following commit(s) were added to refs/heads/master by this push:
new 36c51c1834 Core: Fix NPE in SnapshotUtil when parent snapshot ID is null (#6005)
36c51c1834 is described below
commit 36c51c1834e4f1feecdc4b677394053a05ffd672
Author: liliwei <hi...@gmail.com>
AuthorDate: Wed Oct 19 03:30:12 2022 +0800
Core: Fix NPE in SnapshotUtil when parent snapshot ID is null (#6005)
---
.../java/org/apache/iceberg/util/SnapshotUtil.java | 2 +-
.../org/apache/iceberg/util/TestSnapshotUtil.java | 158 +++++++++++++++++++++
2 files changed, 159 insertions(+), 1 deletion(-)
diff --git a/core/src/main/java/org/apache/iceberg/util/SnapshotUtil.java b/core/src/main/java/org/apache/iceberg/util/SnapshotUtil.java
index 93880f97cb..a3492ad119 100644
--- a/core/src/main/java/org/apache/iceberg/util/SnapshotUtil.java
+++ b/core/src/main/java/org/apache/iceberg/util/SnapshotUtil.java
@@ -72,7 +72,7 @@ public class SnapshotUtil {
public static boolean isParentAncestorOf(
Table table, long snapshotId, long ancestorParentSnapshotId) {
for (Snapshot snapshot : ancestorsOf(snapshotId, table::snapshot)) {
- if (snapshot.parentId() == ancestorParentSnapshotId) {
+ if (snapshot.parentId() != null && snapshot.parentId() == ancestorParentSnapshotId) {
return true;
}
}
diff --git a/core/src/test/java/org/apache/iceberg/util/TestSnapshotUtil.java b/core/src/test/java/org/apache/iceberg/util/TestSnapshotUtil.java
new file mode 100644
index 0000000000..20c1034ce6
--- /dev/null
+++ b/core/src/test/java/org/apache/iceberg/util/TestSnapshotUtil.java
@@ -0,0 +1,158 @@
+/*
+ * 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.util;
+
+import static org.apache.iceberg.types.Types.NestedField.required;
+
+import java.io.File;
+import java.util.List;
+import java.util.stream.StreamSupport;
+import org.apache.iceberg.DataFile;
+import org.apache.iceberg.DataFiles;
+import org.apache.iceberg.PartitionSpec;
+import org.apache.iceberg.Schema;
+import org.apache.iceberg.Snapshot;
+import org.apache.iceberg.TestTables;
+import org.apache.iceberg.types.Types;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class TestSnapshotUtil {
+ @Rule public TemporaryFolder temp = new TemporaryFolder();
+
+ // Schema passed to create tables
+ public static final Schema SCHEMA =
+ new Schema(
+ required(3, "id", Types.IntegerType.get()), required(4, "data", Types.StringType.get()));
+
+ // Partition spec used to create tables
+ protected static final PartitionSpec SPEC = PartitionSpec.builderFor(SCHEMA).build();
+
+ protected File tableDir = null;
+ protected File metadataDir = null;
+ public TestTables.TestTable table = null;
+
+ static final DataFile FILE_A =
+ DataFiles.builder(SPEC)
+ .withPath("/path/to/data-a.parquet")
+ .withFileSizeInBytes(10)
+ .withRecordCount(1)
+ .build();
+
+ private long snapshotAId;
+
+ private long snapshotATimestamp;
+ private long snapshotBId;
+ private long snapshotCId;
+ private long snapshotDId;
+
+ @Before
+ public void before() throws Exception {
+ this.tableDir = temp.newFolder();
+ tableDir.delete(); // created by table create
+
+ this.metadataDir = new File(tableDir, "metadata");
+
+ this.table = TestTables.create(tableDir, "test", SCHEMA, SPEC, 2);
+ table.newFastAppend().appendFile(FILE_A).commit();
+ this.snapshotAId = table.currentSnapshot().snapshotId();
+
+ snapshotATimestamp = System.currentTimeMillis();
+
+ table.newFastAppend().appendFile(FILE_A).commit();
+ this.snapshotBId = table.currentSnapshot().snapshotId();
+
+ table.newFastAppend().appendFile(FILE_A).commit();
+ this.snapshotDId = table.currentSnapshot().snapshotId();
+
+ String branchName = "b1";
+ table.manageSnapshots().createBranch(branchName, snapshotAId).commit();
+ table.newFastAppend().appendFile(FILE_A).toBranch(branchName).commit();
+ this.snapshotCId = table.snapshot(branchName).snapshotId();
+ }
+
+ @After
+ public void cleanupTables() {
+ TestTables.clearTables();
+ }
+
+ @Test
+ public void isParentAncestorOf() {
+ Assert.assertTrue(SnapshotUtil.isParentAncestorOf(table, snapshotBId, snapshotAId));
+ Assert.assertFalse(SnapshotUtil.isParentAncestorOf(table, snapshotCId, snapshotBId));
+ }
+
+ @Test
+ public void isAncestorOf() {
+ Assert.assertTrue(SnapshotUtil.isAncestorOf(table, snapshotBId, snapshotAId));
+ Assert.assertFalse(SnapshotUtil.isAncestorOf(table, snapshotCId, snapshotBId));
+
+ Assert.assertTrue(SnapshotUtil.isAncestorOf(table, snapshotBId));
+ Assert.assertFalse(SnapshotUtil.isAncestorOf(table, snapshotCId));
+ }
+
+ @Test
+ public void currentAncestors() {
+ Iterable<Snapshot> snapshots = SnapshotUtil.currentAncestors(table);
+ expectedSnapshots(new long[] {snapshotDId, snapshotBId, snapshotAId}, snapshots);
+
+ List<Long> snapshotList = SnapshotUtil.currentAncestorIds(table);
+ Assert.assertArrayEquals(
+ new Long[] {snapshotDId, snapshotBId, snapshotAId}, snapshotList.toArray(new Long[0]));
+ }
+
+ @Test
+ public void oldestAncestor() {
+ Snapshot snapshot = SnapshotUtil.oldestAncestor(table);
+ Assert.assertEquals(snapshotAId, snapshot.snapshotId());
+
+ snapshot = SnapshotUtil.oldestAncestorOf(table, snapshotDId);
+ Assert.assertEquals(snapshotAId, snapshot.snapshotId());
+
+ snapshot = SnapshotUtil.oldestAncestorAfter(table, snapshotATimestamp);
+ Assert.assertEquals(snapshotBId, snapshot.snapshotId());
+ }
+
+ @Test
+ public void snapshotsBetween() {
+ List<Long> snapshotIdsBetween =
+ SnapshotUtil.snapshotIdsBetween(table, snapshotAId, snapshotDId);
+ Assert.assertArrayEquals(
+ new Long[] {snapshotDId, snapshotBId}, snapshotIdsBetween.toArray(new Long[0]));
+
+ Iterable<Snapshot> ancestorsBetween =
+ SnapshotUtil.ancestorsBetween(table, snapshotDId, snapshotBId);
+ expectedSnapshots(new long[] {snapshotDId}, ancestorsBetween);
+
+ ancestorsBetween = SnapshotUtil.ancestorsBetween(table, snapshotDId, snapshotCId);
+ expectedSnapshots(new long[] {snapshotDId, snapshotBId, snapshotAId}, ancestorsBetween);
+ }
+
+ private void expectedSnapshots(long[] snapshotIdExpected, Iterable<Snapshot> snapshotsActual) {
+ long[] actualSnapshots =
+ StreamSupport.stream(snapshotsActual.spliterator(), false)
+ .mapToLong(Snapshot::snapshotId)
+ .toArray();
+ Assert.assertArrayEquals(snapshotIdExpected, actualSnapshots);
+ }
+}