You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@polaris.apache.org by di...@apache.org on 2026/06/10 15:26:27 UTC

(polaris) branch main updated: Print exception stack trace when admin purge command fails (#4632)

This is an automated email from the ASF dual-hosted git repository.

dimas-b pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git


The following commit(s) were added to refs/heads/main by this push:
     new ef9fb74cbd Print exception stack trace when admin purge command fails (#4632)
ef9fb74cbd is described below

commit ef9fb74cbd25bafda2bf39c8f4c38f981028135e
Author: vignesh_A <16...@users.noreply.github.com>
AuthorDate: Wed Jun 10 20:56:22 2026 +0530

    Print exception stack trace when admin purge command fails (#4632)
---
 CHANGELOG.md                                       |  1 +
 .../org/apache/polaris/admintool/PurgeCommand.java |  1 +
 .../apache/polaris/admintool/PurgeCommandTest.java | 99 ++++++++++++++++++++++
 3 files changed, 101 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7e0c6d528c..0a75937528 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -62,6 +62,7 @@ request adding CHANGELOG notes for breaking (!) changes and possibly other secti
 
 ### Fixes
 - `RateLimiterFilter` now returns an Iceberg-compatible `ErrorResponse` JSON body on HTTP 429, with `Content-Type: application/json`. Previously the body was empty, causing Iceberg REST clients to surface an opaque error.
+- The admin tool `purge` command now prints the underlying exception stack trace to stderr when a purge fails unexpectedly, matching the `bootstrap` command. Previously a failed purge printed only a generic message, giving operators no diagnostic information.
 
 ## [1.5.0]
 
diff --git a/runtime/admin/src/main/java/org/apache/polaris/admintool/PurgeCommand.java b/runtime/admin/src/main/java/org/apache/polaris/admintool/PurgeCommand.java
index 772d311d6b..426bea9d01 100644
--- a/runtime/admin/src/main/java/org/apache/polaris/admintool/PurgeCommand.java
+++ b/runtime/admin/src/main/java/org/apache/polaris/admintool/PurgeCommand.java
@@ -59,6 +59,7 @@ public class PurgeCommand extends BaseMetaStoreCommand {
       spec.commandLine().getErr().printf("Purge encountered errors during operation.");
       return EXIT_CODE_PURGE_ERROR;
     } catch (Exception e) {
+      e.printStackTrace(spec.commandLine().getErr());
       spec.commandLine().getErr().println("Purge encountered errors during operation.");
       return EXIT_CODE_PURGE_ERROR;
     }
diff --git a/runtime/admin/src/test/java/org/apache/polaris/admintool/PurgeCommandTest.java b/runtime/admin/src/test/java/org/apache/polaris/admintool/PurgeCommandTest.java
new file mode 100644
index 0000000000..27afe165d3
--- /dev/null
+++ b/runtime/admin/src/test/java/org/apache/polaris/admintool/PurgeCommandTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.polaris.admintool;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Map;
+import org.apache.polaris.core.config.RealmConfig;
+import org.apache.polaris.core.context.RealmContext;
+import org.apache.polaris.core.persistence.BasePersistence;
+import org.apache.polaris.core.persistence.MetaStoreManagerFactory;
+import org.apache.polaris.core.persistence.PolarisMetaStoreManager;
+import org.apache.polaris.core.persistence.bootstrap.RootCredentialsSet;
+import org.apache.polaris.core.persistence.cache.EntityCache;
+import org.apache.polaris.core.persistence.dao.entity.BaseResult;
+import org.apache.polaris.core.persistence.dao.entity.PrincipalSecretsResult;
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
+
+class PurgeCommandTest {
+
+  /**
+   * When {@code purgeRealms} fails (e.g. database connection failure, permission denied), the
+   * command must surface the underlying exception to stderr so operators can diagnose it, in
+   * addition to the generic error message and the purge error exit code.
+   */
+  @Test
+  void testPurgeFailurePrintsStackTrace() {
+    String failureMessage = "simulated database connection failure";
+    PurgeCommand command = new PurgeCommand();
+    command.metaStoreManagerFactory =
+        new ThrowingMetaStoreManagerFactory(new RuntimeException(failureMessage));
+
+    CommandLine commandLine = new CommandLine(command);
+    StringWriter err = new StringWriter();
+    commandLine.setErr(new PrintWriter(err));
+
+    int exitCode = commandLine.execute("-r", "realm1");
+
+    assertThat(exitCode).isEqualTo(BaseCommand.EXIT_CODE_PURGE_ERROR);
+    assertThat(err.toString())
+        .contains("java.lang.RuntimeException: " + failureMessage)
+        .contains("\tat ")
+        .contains("Purge encountered errors during operation.");
+  }
+
+  /** Minimal {@link MetaStoreManagerFactory} whose {@code purgeRealms} always throws. */
+  private static final class ThrowingMetaStoreManagerFactory implements MetaStoreManagerFactory {
+    private final RuntimeException failure;
+
+    private ThrowingMetaStoreManagerFactory(RuntimeException failure) {
+      this.failure = failure;
+    }
+
+    @Override
+    public Map<String, BaseResult> purgeRealms(Iterable<String> realms) {
+      throw failure;
+    }
+
+    @Override
+    public PolarisMetaStoreManager getOrCreateMetaStoreManager(RealmContext realmContext) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public BasePersistence getOrCreateSession(RealmContext realmContext) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EntityCache getOrCreateEntityCache(RealmContext realmContext, RealmConfig realmConfig) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, PrincipalSecretsResult> bootstrapRealms(
+        Iterable<String> realms, RootCredentialsSet rootCredentialsSet) {
+      throw new UnsupportedOperationException();
+    }
+  }
+}