This is an automated email from the ASF dual-hosted git repository. ayushsaxena pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hive.git
The following commit(s) were added to refs/heads/master by this push: new a57e5805427 HIVE-28274: Iceberg: Add support for 'If Not Exists' and 'or Replace' for Create Branch. (#5259). (Ayush Saxena, reviewed by Butao Zhang) a57e5805427 is described below commit a57e580542701a5acdf4e66ce99b52bae90b4d63 Author: Ayush Saxena <ayushsax...@apache.org> AuthorDate: Mon May 27 13:36:23 2024 +0530 HIVE-28274: Iceberg: Add support for 'If Not Exists' and 'or Replace' for Create Branch. (#5259). (Ayush Saxena, reviewed by Butao Zhang) --- .../iceberg/mr/hive/IcebergSnapshotRefExec.java | 45 +++++++++++++-- .../mr/hive/TestHiveIcebergBranchOperation.java | 28 +++++++++ .../queries/positive/alter_table_create_branch.q | 11 +++- .../positive/alter_table_create_branch.q.out | 66 ++++++++++++++++++++-- .../results/positive/alter_table_create_tag.q.out | 4 +- .../hadoop/hive/ql/parse/AlterClauseParser.g | 6 +- .../AlterTableCreateSnapshotRefAnalyzer.java | 10 +++- .../hive/ql/parse/AlterTableSnapshotRefSpec.java | 17 +++++- 8 files changed, 169 insertions(+), 18 deletions(-) diff --git a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergSnapshotRefExec.java b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergSnapshotRefExec.java index 6332cbb767f..3e251913801 100644 --- a/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergSnapshotRefExec.java +++ b/iceberg/iceberg-handler/src/main/java/org/apache/iceberg/mr/hive/IcebergSnapshotRefExec.java @@ -22,6 +22,7 @@ package org.apache.iceberg.mr.hive; import java.util.Optional; import org.apache.hadoop.hive.ql.parse.AlterTableSnapshotRefSpec; import org.apache.iceberg.ManageSnapshots; +import org.apache.iceberg.Snapshot; import org.apache.iceberg.SnapshotRef; import org.apache.iceberg.Table; import org.apache.iceberg.util.SnapshotUtil; @@ -42,6 +43,11 @@ public class IcebergSnapshotRefExec { */ public static void createBranch(Table table, AlterTableSnapshotRefSpec.CreateSnapshotRefSpec createBranchSpec) { String branchName = createBranchSpec.getRefName(); + boolean refExistsAsBranch = refExistsAsBranch(table, branchName); + if (createBranchSpec.isIfNotExists() && refExistsAsBranch) { + return; + } + boolean isReplace = createBranchSpec.isReplace() && refExistsAsBranch; Long snapshotId = null; if (createBranchSpec.getSnapshotId() != null) { snapshotId = createBranchSpec.getSnapshotId(); @@ -56,16 +62,25 @@ public class IcebergSnapshotRefExec { throw new IllegalArgumentException(String.format("Tag %s does not exist", tagName)); } } else { - snapshotId = Optional.ofNullable(table.currentSnapshot()).map(snapshot -> snapshot.snapshotId()).orElse(null); + snapshotId = Optional.ofNullable(table.currentSnapshot()).map(Snapshot::snapshotId).orElse(null); } ManageSnapshots manageSnapshots = table.manageSnapshots(); if (snapshotId != null) { - LOG.info("Creating a branch {} on an iceberg table {} with snapshotId {}", branchName, table.name(), snapshotId); - manageSnapshots.createBranch(branchName, snapshotId); + createOrReplaceBranch(manageSnapshots, table, isReplace, branchName, snapshotId); + } else if (isReplace) { + throw new IllegalArgumentException( + "Cannot complete replace branch operation on " + branchName + ", main has no snapshot"); } else { LOG.info("Creating a branch {} on an empty iceberg table {}", branchName, table.name()); manageSnapshots.createBranch(branchName); } + + setCreateBranchOptionalParams(createBranchSpec, manageSnapshots, branchName); + manageSnapshots.commit(); + } + + private static void setCreateBranchOptionalParams(AlterTableSnapshotRefSpec.CreateSnapshotRefSpec createBranchSpec, + ManageSnapshots manageSnapshots, String branchName) { if (createBranchSpec.getMaxRefAgeMs() != null) { manageSnapshots.setMaxRefAgeMs(branchName, createBranchSpec.getMaxRefAgeMs()); } @@ -75,8 +90,30 @@ public class IcebergSnapshotRefExec { if (createBranchSpec.getMaxSnapshotAgeMs() != null) { manageSnapshots.setMaxSnapshotAgeMs(branchName, createBranchSpec.getMaxSnapshotAgeMs()); } + } - manageSnapshots.commit(); + private static void createOrReplaceBranch(ManageSnapshots manageSnapshots, Table table, boolean isReplace, + String branchName, Long snapshotId) { + if (isReplace) { + LOG.info("Replacing branch {} on an iceberg table {} with snapshotId {}", branchName, table.name(), snapshotId); + manageSnapshots.replaceBranch(branchName, snapshotId); + } else { + LOG.info("Creating a branch {} on an iceberg table {} with snapshotId {}", branchName, table.name(), snapshotId); + manageSnapshots.createBranch(branchName, snapshotId); + } + } + + private static boolean refExistsAsBranch(Table table, String branchName) { + SnapshotRef branchRef = table.refs().get(branchName); + if (branchRef != null) { + if (branchRef.isBranch()) { + return true; + } else { + throw new IllegalArgumentException( + "Cannot complete replace branch operation on " + branchName + ", as it exists as Tag"); + } + } + return false; } public static void dropBranch(Table table, AlterTableSnapshotRefSpec.DropSnapshotRefSpec dropBranchSpec) { diff --git a/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergBranchOperation.java b/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergBranchOperation.java index 9f88bae3496..c5eb59987fb 100644 --- a/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergBranchOperation.java +++ b/iceberg/iceberg-handler/src/test/java/org/apache/iceberg/mr/hive/TestHiveIcebergBranchOperation.java @@ -252,4 +252,32 @@ public class TestHiveIcebergBranchOperation extends HiveIcebergStorageHandlerWit // Select with non-lower case branch name shouldn't throw exception. shell.executeStatement(String.format("SELECT * FROM default.customers.branch_%s", branchName)); } + + @Test + public void testCreateOrReplaceBranchWithTag() throws InterruptedException, IOException { + Table table = + testTables.createTableWithVersions(shell, "customers", HiveIcebergStorageHandlerTestUtils.CUSTOMER_SCHEMA, + fileFormat, HiveIcebergStorageHandlerTestUtils.CUSTOMER_RECORDS, 2); + + Long snapshotId = table.history().get(0).snapshotId(); + shell.executeStatement( + String.format("ALTER TABLE customers CREATE TAG %s FOR SYSTEM_VERSION AS OF %d", "test1", snapshotId)); + + Assertions.assertThatThrownBy(() -> shell.executeStatement("ALTER TABLE customers CREATE OR REPLACE BRANCH test1")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Cannot complete replace branch operation on test1, as it exists as Tag"); + } + + @Test + public void testCreateOrReplaceBranchWithEmptyTable() throws IOException { + Table table = + testTables.createTable(shell, "customers", HiveIcebergStorageHandlerTestUtils.CUSTOMER_SCHEMA, fileFormat, null, + 2); + + Assert.assertEquals(0, table.history().size()); + shell.executeStatement("ALTER TABLE customers CREATE BRANCH test1"); + Assertions.assertThatThrownBy(() -> shell.executeStatement("ALTER TABLE customers CREATE OR REPLACE BRANCH test1")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(" Cannot complete replace branch operation on test1, main has no snapshot"); + } } diff --git a/iceberg/iceberg-handler/src/test/queries/positive/alter_table_create_branch.q b/iceberg/iceberg-handler/src/test/queries/positive/alter_table_create_branch.q index 6e83bef5a4c..55581ce6968 100644 --- a/iceberg/iceberg-handler/src/test/queries/positive/alter_table_create_branch.q +++ b/iceberg/iceberg-handler/src/test/queries/positive/alter_table_create_branch.q @@ -9,7 +9,7 @@ alter table iceTbl create branch test_branch_0; insert into iceTbl values(1, 'jack'); --- create s branch test_branch_1 with default values based on the current snapshotId +-- create a branch test_branch_1 with default values based on the current snapshotId explain alter table iceTbl create branch test_branch_1; alter table iceTbl create branch test_branch_1; -- check the values, one value @@ -41,6 +41,15 @@ alter table iceTbl create tag test_tag; explain alter table iceTbl create branch test_branch_10 for tag as of test_tag; alter table iceTbl create branch test_branch_10 for tag as of test_tag; +-- create a branch which already exists +explain alter table iceTbl create branch if not exists test_branch_10; +alter table iceTbl create branch if not exists test_branch_10; + +-- create or replace +explain alter table iceTbl create or replace branch test_branch_1; +alter table iceTbl create or replace branch test_branch_1; +select * from default.iceTbl.branch_test_branch_1; + -- drop a branch explain alter table iceTbl drop branch test_branch_3; alter table iceTbl drop branch test_branch_3; diff --git a/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_branch.q.out b/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_branch.q.out index f86670d182a..9aa8e112c4c 100644 --- a/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_branch.q.out +++ b/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_branch.q.out @@ -19,7 +19,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_0}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_0, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_0 PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -48,7 +48,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_1}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_1, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_1 PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -86,7 +86,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_2, maxRefAgeMs=432000000}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_2, maxRefAgeMs=432000000, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_2 retain 5 days PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -125,7 +125,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_3, minSnapshotsToKeep=5}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_3, minSnapshotsToKeep=5, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_3 with snapshot retention 5 snapshots PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -165,7 +165,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_4, minSnapshotsToKeep=5, maxSnapshotAgeMs=432000000}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_4, minSnapshotsToKeep=5, maxSnapshotAgeMs=432000000, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_4 with snapshot retention 5 snapshots 5 days PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -204,7 +204,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_10}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_10, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create branch test_branch_10 for tag as of test_tag PREHOOK: type: ALTERTABLE_CREATEBRANCH @@ -212,6 +212,60 @@ PREHOOK: Input: default@icetbl POSTHOOK: query: alter table iceTbl create branch test_branch_10 for tag as of test_tag POSTHOOK: type: ALTERTABLE_CREATEBRANCH POSTHOOK: Input: default@icetbl +PREHOOK: query: explain alter table iceTbl create branch if not exists test_branch_10 +PREHOOK: type: ALTERTABLE_CREATEBRANCH +PREHOOK: Input: default@icetbl +POSTHOOK: query: explain alter table iceTbl create branch if not exists test_branch_10 +POSTHOOK: type: ALTERTABLE_CREATEBRANCH +POSTHOOK: Input: default@icetbl +STAGE DEPENDENCIES: + Stage-0 is a root stage + +STAGE PLANS: + Stage: Stage-0 + SnapshotRef Operation + table name: default.iceTbl + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_10, isReplace=false, ifNotExists=true}} + +PREHOOK: query: alter table iceTbl create branch if not exists test_branch_10 +PREHOOK: type: ALTERTABLE_CREATEBRANCH +PREHOOK: Input: default@icetbl +POSTHOOK: query: alter table iceTbl create branch if not exists test_branch_10 +POSTHOOK: type: ALTERTABLE_CREATEBRANCH +POSTHOOK: Input: default@icetbl +PREHOOK: query: explain alter table iceTbl create or replace branch test_branch_1 +PREHOOK: type: ALTERTABLE_CREATEBRANCH +PREHOOK: Input: default@icetbl +POSTHOOK: query: explain alter table iceTbl create or replace branch test_branch_1 +POSTHOOK: type: ALTERTABLE_CREATEBRANCH +POSTHOOK: Input: default@icetbl +STAGE DEPENDENCIES: + Stage-0 is a root stage + +STAGE PLANS: + Stage: Stage-0 + SnapshotRef Operation + table name: default.iceTbl + spec: AlterTableSnapshotRefSpec{operationType=CREATE_BRANCH, operationParams=CreateSnapshotRefSpec{refName=test_branch_1, isReplace=true, ifNotExists=false}} + +PREHOOK: query: alter table iceTbl create or replace branch test_branch_1 +PREHOOK: type: ALTERTABLE_CREATEBRANCH +PREHOOK: Input: default@icetbl +POSTHOOK: query: alter table iceTbl create or replace branch test_branch_1 +POSTHOOK: type: ALTERTABLE_CREATEBRANCH +POSTHOOK: Input: default@icetbl +PREHOOK: query: select * from default.iceTbl.branch_test_branch_1 +PREHOOK: type: QUERY +PREHOOK: Input: default@icetbl +PREHOOK: Output: hdfs://### HDFS PATH ### +POSTHOOK: query: select * from default.iceTbl.branch_test_branch_1 +POSTHOOK: type: QUERY +POSTHOOK: Input: default@icetbl +POSTHOOK: Output: hdfs://### HDFS PATH ### +1 jack +2 bob +3 tom +4 lisa PREHOOK: query: explain alter table iceTbl drop branch test_branch_3 PREHOOK: type: ALTERTABLE_DROPBRANCH PREHOOK: Input: default@icetbl diff --git a/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_tag.q.out b/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_tag.q.out index 312a93f7f05..803891e4b28 100644 --- a/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_tag.q.out +++ b/iceberg/iceberg-handler/src/test/results/positive/alter_table_create_tag.q.out @@ -27,7 +27,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_TAG, operationParams=CreateSnapshotRefSpec{refName=test_tag_1}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_TAG, operationParams=CreateSnapshotRefSpec{refName=test_tag_1, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create tag test_tag_1 PREHOOK: type: ALTERTABLE_CREATETAG @@ -65,7 +65,7 @@ STAGE PLANS: Stage: Stage-0 SnapshotRef Operation table name: default.iceTbl - spec: AlterTableSnapshotRefSpec{operationType=CREATE_TAG, operationParams=CreateSnapshotRefSpec{refName=test_tag_2, maxRefAgeMs=432000000}} + spec: AlterTableSnapshotRefSpec{operationType=CREATE_TAG, operationParams=CreateSnapshotRefSpec{refName=test_tag_2, maxRefAgeMs=432000000, isReplace=false, ifNotExists=false}} PREHOOK: query: alter table iceTbl create tag test_tag_2 retain 5 days PREHOOK: type: ALTERTABLE_CREATETAG diff --git a/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g b/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g index 5d3c837b0bd..2f136a5d998 100644 --- a/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g +++ b/parser/src/java/org/apache/hadoop/hive/ql/parse/AlterClauseParser.g @@ -539,8 +539,10 @@ alterStatementSuffixDropBranch alterStatementSuffixCreateBranch @init { gParent.pushMsg("alter table create branch", state); } @after { gParent.popMsg(state); } - : KW_CREATE KW_BRANCH branchName=identifier snapshotIdOfRef? refRetain? retentionOfSnapshots? - -> ^(TOK_ALTERTABLE_CREATE_BRANCH $branchName snapshotIdOfRef? refRetain? retentionOfSnapshots?) + : KW_CREATE KW_BRANCH ifNotExists? branchName=identifier snapshotIdOfRef? refRetain? retentionOfSnapshots? + -> ^(TOK_ALTERTABLE_CREATE_BRANCH $branchName ifNotExists? snapshotIdOfRef? refRetain? retentionOfSnapshots?) + | KW_CREATE KW_OR KW_REPLACE KW_BRANCH branchName=identifier snapshotIdOfRef? refRetain? retentionOfSnapshots? + -> ^(TOK_ALTERTABLE_CREATE_BRANCH $branchName KW_REPLACE snapshotIdOfRef? refRetain? retentionOfSnapshots?) ; snapshotIdOfRef diff --git a/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/snapshotref/AlterTableCreateSnapshotRefAnalyzer.java b/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/snapshotref/AlterTableCreateSnapshotRefAnalyzer.java index 3b581ddfa0b..b4ec5380e3a 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/snapshotref/AlterTableCreateSnapshotRefAnalyzer.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/ddl/table/snapshotref/AlterTableCreateSnapshotRefAnalyzer.java @@ -62,6 +62,8 @@ public abstract class AlterTableCreateSnapshotRefAnalyzer extends AbstractAlterT Long maxRefAgeMs = null; Integer minSnapshotsToKeep = null; Long maxSnapshotAgeMs = null; + boolean isReplace = false; + boolean ifNotExists = false; String asOfTag = null; for (int i = 1; i < command.getChildCount(); i++) { ASTNode childNode = (ASTNode) command.getChild(i); @@ -93,6 +95,12 @@ public abstract class AlterTableCreateSnapshotRefAnalyzer extends AbstractAlterT .toMillis(Long.parseLong(maxSnapshotAge)); } break; + case HiveParser.KW_REPLACE: + isReplace = true; + break; + case HiveParser.TOK_IFNOTEXISTS: + ifNotExists = true; + break; default: throw new SemanticException("Unrecognized token in ALTER " + alterTableType.getName() + " statement"); } @@ -100,7 +108,7 @@ public abstract class AlterTableCreateSnapshotRefAnalyzer extends AbstractAlterT AlterTableSnapshotRefSpec.CreateSnapshotRefSpec createSnapshotRefSpec = new AlterTableSnapshotRefSpec.CreateSnapshotRefSpec(refName, snapshotId, asOfTime, - maxRefAgeMs, minSnapshotsToKeep, maxSnapshotAgeMs, asOfTag); + maxRefAgeMs, minSnapshotsToKeep, maxSnapshotAgeMs, asOfTag, isReplace, ifNotExists); AlterTableSnapshotRefSpec<AlterTableSnapshotRefSpec.CreateSnapshotRefSpec> alterTableSnapshotRefSpec = new AlterTableSnapshotRefSpec(alterTableType, createSnapshotRefSpec); AbstractAlterTableDesc alterTableDesc = diff --git a/ql/src/java/org/apache/hadoop/hive/ql/parse/AlterTableSnapshotRefSpec.java b/ql/src/java/org/apache/hadoop/hive/ql/parse/AlterTableSnapshotRefSpec.java index 675f9ee9d05..0dcf08f1e9c 100644 --- a/ql/src/java/org/apache/hadoop/hive/ql/parse/AlterTableSnapshotRefSpec.java +++ b/ql/src/java/org/apache/hadoop/hive/ql/parse/AlterTableSnapshotRefSpec.java @@ -56,6 +56,8 @@ public class AlterTableSnapshotRefSpec<T> { private Integer minSnapshotsToKeep; private Long maxSnapshotAgeMs; private String asOfTag; + private boolean isReplace; + private boolean ifNotExists; public String getRefName() { return refName; @@ -85,8 +87,16 @@ public class AlterTableSnapshotRefSpec<T> { return asOfTag; } + public boolean isReplace() { + return isReplace; + } + + public boolean isIfNotExists() { + return ifNotExists; + } + public CreateSnapshotRefSpec(String refName, Long snapShotId, Long asOfTime, Long maxRefAgeMs, - Integer minSnapshotsToKeep, Long maxSnapshotAgeMs, String asOfTag) { + Integer minSnapshotsToKeep, Long maxSnapshotAgeMs, String asOfTag, boolean isReplace, boolean ifNotExists) { this.refName = refName; this.snapshotId = snapShotId; this.asOfTime = asOfTime; @@ -94,12 +104,15 @@ public class AlterTableSnapshotRefSpec<T> { this.minSnapshotsToKeep = minSnapshotsToKeep; this.maxSnapshotAgeMs = maxSnapshotAgeMs; this.asOfTag = asOfTag; + this.isReplace = isReplace; + this.ifNotExists = ifNotExists; } public String toString() { return MoreObjects.toStringHelper(this).add("refName", refName).add("snapshotId", snapshotId) .add("asOfTime", asOfTime).add("maxRefAgeMs", maxRefAgeMs).add("minSnapshotsToKeep", minSnapshotsToKeep) - .add("maxSnapshotAgeMs", maxSnapshotAgeMs).omitNullValues().toString(); + .add("maxSnapshotAgeMs", maxSnapshotAgeMs).add("isReplace", isReplace).add("ifNotExists", ifNotExists) + .omitNullValues().toString(); } } public static class DropSnapshotRefSpec {