This is an automated email from the ASF dual-hosted git repository.
diqiu50 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 880032f6af [#13782] fix(trino-connector): Report authorization and
validation refusals as user errors (#13787)
880032f6af is described below
commit 880032f6af69a997da5ffde82fddf3574c867d51
Author: Yuhui <[email protected]>
AuthorDate: Tue Oct 13 21:44:35 2026 +0800
[#13782] fix(trino-connector): Report authorization and validation refusals
as user errors (#13787)
### What changes were proposed in this pull request?
Classify failures from the Gravitino server in the Trino connector
through a single
`GravitinoErrorCode.toTrinoException`, used by
`CatalogConnectorMetadata`, the catalog
stored procedures and per-user client creation:
- `ForbiddenException` -> `PERMISSION_DENIED`
- `NotInUseException` -> `CATALOG_UNAVAILABLE`
- `ConnectionFailedException`, or a REST failure caused by a network
error -> `GRAVITINO_CONNECTION_FAILED` (new)
- `IllegalArgumentException` -> `GRAVITINO_ILLEGAL_ARGUMENT`
- `UnsupportedOperationException` -> `GRAVITINO_UNSUPPORTED_OPERATION`
- `TrinoException` passes through; others -> `GENERIC_INTERNAL_ERROR`
Messages keep only the first line, dropping the server stack trace. Also
fix the exception
types caught by `createSchema`.
### Why are the changes needed?
Expected refusals and connection failures were reported as
`GENERIC_INTERNAL_ERROR`, so they
looked like Trino faults.
Fix: #13782
### Does this PR introduce _any_ user-facing change?
Yes, these failures use the error codes above, and catalog procedure
errors keep their own codes
instead of `GRAVITINO_UNSUPPORTED_OPERATION`.
### How was this patch tested?
Added `TestGravitinoErrorCode` and `TestCatalogConnectorMetadataErrors`,
and extended the
stored procedure and forward-user tests.
---
.../testsets/jdbc-mysql/00005_create_catalog.txt | 2 +-
.../trino/connector/GravitinoConnector.java | 32 +--
.../trino/connector/GravitinoErrorCode.java | 94 ++++++++-
.../connector/catalog/CatalogConnectorManager.java | 3 +-
.../catalog/CatalogConnectorMetadata.java | 76 +++++--
.../AlterCatalogStoredProcedure.java | 7 +-
.../CreateCatalogStoredProcedure.java | 16 +-
.../DropCatalogStoredProcedure.java | 10 +-
.../trino/connector/TestGravitinoConnector.java | 5 +-
.../TestGravitinoConnectorForwardUser.java | 24 +++
.../trino/connector/TestGravitinoErrorCode.java | 167 +++++++++++++++
.../TestCatalogConnectorMetadataErrors.java | 223 +++++++++++++++++++++
.../TestAlterCatalogStoredProcedure.java | 26 +++
.../TestCreateCatalogStoredProcedure.java | 85 ++++++++
.../TestDropCatalogStoredProcedure.java | 29 ++-
15 files changed, 718 insertions(+), 81 deletions(-)
diff --git
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00005_create_catalog.txt
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00005_create_catalog.txt
index d17eaa68f9..6e75e27dd3 100644
---
a/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00005_create_catalog.txt
+++
b/trino-connector/integration-test/src/test/resources/trino-ci-testset/testsets/jdbc-mysql/00005_create_catalog.txt
@@ -10,7 +10,7 @@ CALL
<BLANK_LINE>
-<QUERY_FAILED> Drop catalog failed. Catalog test.gt_mysql_xxx1 not exists
+<QUERY_FAILED> Catalog test.gt_mysql_xxx1 not exists
CALL
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnector.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnector.java
index 0eb06e1495..8f130f2974 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnector.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoConnector.java
@@ -280,7 +280,19 @@ public class GravitinoConnector implements Connector {
return perUserSessionCache.get(
credKey,
() -> {
- GravitinoAdminClient userClient = buildAuthClient(session);
+ GravitinoAdminClient userClient;
+ try {
+ userClient = buildAuthClient(session);
+ } catch (IllegalArgumentException |
UnsupportedOperationException e) {
+ // buildAuthClient raises these for an invalid or
unsupported credential.
+ throw new TrinoException(
+ PERMISSION_DENIED,
+ "Failed to authenticate user '"
+ + session.getUser()
+ + "' with Gravitino: "
+ + e.getMessage(),
+ e);
+ }
GravitinoMetalake userMetalake =
userClient.loadMetalake(catalogConnectorContext.getMetalake().name());
return new UserSession(
@@ -291,20 +303,10 @@ public class GravitinoConnector implements Connector {
Throwable cause = e.getCause() == null ? e : e.getCause();
LOG.warn(
cause, "Failed to create per-user Gravitino client for user '%s'",
session.getUser());
- if (cause instanceof TrinoException) {
- // Already carries a specific Trino error code (e.g. from
buildForSession); re-wrapping
- // would swallow it.
- throw (TrinoException) cause;
- }
- if (cause instanceof IllegalArgumentException
- || cause instanceof UnsupportedOperationException) {
- throw new TrinoException(
- PERMISSION_DENIED,
- "Failed to authenticate user '"
- + session.getUser()
- + "' with Gravitino: "
- + cause.getMessage(),
- cause);
+ if (cause instanceof Exception) {
+ throw GravitinoErrorCode.toTrinoException(
+ (Exception) cause,
+ "Failed to create per-user Gravitino client for user '" +
session.getUser() + "'");
}
throw new TrinoException(
GENERIC_INTERNAL_ERROR,
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
index 059d53cb2b..9d48feb2d0 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/GravitinoErrorCode.java
@@ -19,11 +19,24 @@
package org.apache.gravitino.trino.connector;
import static io.trino.spi.ErrorType.EXTERNAL;
+import static io.trino.spi.StandardErrorCode.CATALOG_UNAVAILABLE;
+import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
import io.trino.spi.ErrorCode;
import io.trino.spi.ErrorCodeSupplier;
import io.trino.spi.ErrorType;
-import java.util.List;
+import io.trino.spi.TrinoException;
+import java.io.InterruptedIOException;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.concurrent.ExecutionException;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.NotInUseException;
+import org.apache.gravitino.exceptions.RESTException;
/** Gravitino error code. */
public enum GravitinoErrorCode implements ErrorCodeSupplier {
@@ -78,7 +91,9 @@ public enum GravitinoErrorCode implements ErrorCodeSupplier {
/** Duplicated catalogs. */
GRAVITINO_DUPLICATED_CATALOGS(24, EXTERNAL),
/** Expression error. */
- GRAVITINO_EXPRESSION_ERROR(25, EXTERNAL);
+ GRAVITINO_EXPRESSION_ERROR(25, EXTERNAL),
+ /** Failed to connect to the Gravitino server or to the underlying source of
a catalog. */
+ GRAVITINO_CONNECTION_FAILED(26, EXTERNAL);
// suppress ImmutableEnumChecker because ErrorCode is outside the project.
@SuppressWarnings("ImmutableEnumChecker")
@@ -94,15 +109,74 @@ public enum GravitinoErrorCode implements
ErrorCodeSupplier {
}
/**
- * Converts an exception's message to a simplified error message. If the
message contains multiple
- * lines, returns the first two lines concatenated. If the message contains
only one line, returns
- * that line.
+ * Converts an exception into a Trino exception. See {@link
#toTrinoException(Exception, String)}.
*
- * @param e the exception whose message should be simplified
- * @return the simplified error message
+ * @param e the exception to convert
+ * @return the Trino exception to throw
*/
- public static String toSimpleErrorMessage(Exception e) {
- List<String> lines = e.getMessage().lines().toList();
- return lines.size() > 1 ? lines.get(0) + lines.get(1) : lines.get(0);
+ public static TrinoException toTrinoException(Exception e) {
+ return toTrinoException(e, null);
+ }
+
+ /**
+ * Converts an exception into a Trino exception. A {@link TrinoException} is
returned as is, so a
+ * caller may route every failure of a guarded block through this method,
including a {@link
+ * TrinoException} it throws inside that block. An {@link
ExecutionException} is classified by its
+ * cause. {@link ForbiddenException}, {@link NotInUseException}, {@link
IllegalArgumentException}
+ * and {@link UnsupportedOperationException} map to {@code
PERMISSION_DENIED}, {@code
+ * CATALOG_UNAVAILABLE}, {@link #GRAVITINO_ILLEGAL_ARGUMENT} and {@link
+ * #GRAVITINO_UNSUPPORTED_OPERATION}. A {@link ConnectionFailedException},
or a {@link
+ * RESTException} caused by a network failure, maps to {@link
#GRAVITINO_CONNECTION_FAILED}.
+ * Anything else maps to {@code GENERIC_INTERNAL_ERROR}.
+ *
+ * @param exception the exception to convert
+ * @param context describes the failed operation; it prefixes the message of
an exception that
+ * maps to {@code GENERIC_INTERNAL_ERROR}, whose own message may not say
what failed
+ * @return the Trino exception to throw
+ */
+ public static TrinoException toTrinoException(Exception exception, @Nullable
String context) {
+ Exception e =
+ exception instanceof ExecutionException && exception.getCause()
instanceof Exception
+ ? (Exception) exception.getCause()
+ : exception;
+ if (e instanceof TrinoException) {
+ return (TrinoException) e;
+ }
+ if (e instanceof ForbiddenException) {
+ return new TrinoException(PERMISSION_DENIED, firstLineMessage(e), e);
+ }
+ if (e instanceof NotInUseException) {
+ return new TrinoException(CATALOG_UNAVAILABLE, firstLineMessage(e), e);
+ }
+ if (e instanceof ConnectionFailedException
+ || (e instanceof RESTException && isNetworkFailure(e.getCause()))) {
+ return new TrinoException(GRAVITINO_CONNECTION_FAILED,
firstLineMessage(e), e);
+ }
+ if (e instanceof IllegalArgumentException) {
+ return new TrinoException(GRAVITINO_ILLEGAL_ARGUMENT,
firstLineMessage(e), e);
+ }
+ if (e instanceof UnsupportedOperationException) {
+ return new TrinoException(GRAVITINO_UNSUPPORTED_OPERATION,
firstLineMessage(e), e);
+ }
+ String message = firstLineMessage(e);
+ return new TrinoException(
+ GENERIC_INTERNAL_ERROR, context == null ? message : context + ": " +
message, e);
+ }
+
+ // The REST client wraps every IOException in a RESTException, including
JSON (de)serialization
+ // failures, so only socket-level causes mean the server could not be
reached.
+ private static boolean isNetworkFailure(@Nullable Throwable cause) {
+ return cause instanceof SocketException
+ || cause instanceof UnknownHostException
+ || cause instanceof InterruptedIOException;
+ }
+
+ // Keeps the first non-blank line of the message. A failure received over
REST carries the server
+ // stack trace after it, and other failures may add details there as well;
the full text stays
+ // available in the cause.
+ private static String firstLineMessage(Exception e) {
+ return StringUtils.isBlank(e.getMessage())
+ ? e.toString()
+ :
e.getMessage().lines().filter(StringUtils::isNotBlank).findFirst().orElse(e.toString());
}
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
index 1446d97dbc..ab9b3e80d8 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorManager.java
@@ -409,8 +409,7 @@ public class CatalogConnectorManager {
// Report the root cause: the actual reason a registration failed, such as
"Access Denied:
// Cannot create catalog", is wrapped in several layers of TrinoException
by the time it gets
// here, and the outer messages say nothing a user can act on. The
outermost message is kept
- // as a prefix because it names the subsystem that failed. Do not use
- // GravitinoErrorCode.toSimpleErrorMessage(), it throws on an exception
with no message.
+ // as a prefix because it names the subsystem that failed.
Throwable rootCause;
try {
rootCause = Throwables.getRootCause(e);
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
index 4e71662872..77c2e2a4c2 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/catalog/CatalogConnectorMetadata.java
@@ -34,9 +34,11 @@ import org.apache.gravitino.Schema;
import org.apache.gravitino.SupportsSchemas;
import org.apache.gravitino.client.GravitinoMetalake;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchFunctionException;
import org.apache.gravitino.exceptions.NoSuchSchemaException;
import org.apache.gravitino.exceptions.NoSuchTableException;
import org.apache.gravitino.exceptions.NonEmptySchemaException;
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
import org.apache.gravitino.exceptions.TableAlreadyExistsException;
import org.apache.gravitino.function.Function;
import org.apache.gravitino.function.FunctionCatalog;
@@ -90,6 +92,8 @@ public class CatalogConnectorMetadata {
GravitinoErrorCode.GRAVITINO_UNSUPPORTED_OPERATION,
"Catalog does not support schema or table operations." +
e.getMessage(),
e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -104,6 +108,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchCatalogException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS,
CATALOG_DOES_NOT_EXIST_MSG, e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -112,7 +118,7 @@ public class CatalogConnectorMetadata {
*
* @param schemaName the name of the schema
* @return the Gravitino schema
- * @throws TrinoException if the schema is not found
+ * @throws TrinoException if the schema is not found or the request fails
*/
public GravitinoSchema getSchema(String schemaName) {
try {
@@ -121,6 +127,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchSchemaException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EXISTS,
SCHEMA_DOES_NOT_EXIST_MSG, e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -130,7 +138,7 @@ public class CatalogConnectorMetadata {
* @param schemaName the name of the schema
* @param tableName the name of the table
* @return the Gravitino table
- * @throws TrinoException if the table is not found
+ * @throws TrinoException if the table is not found or the request fails
*/
public GravitinoTable getTable(String schemaName, String tableName) {
try {
@@ -139,6 +147,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchTableException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_TABLE_NOT_EXISTS, "Table does not
exist", e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -155,6 +165,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchSchemaException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EXISTS,
SCHEMA_DOES_NOT_EXIST_MSG, e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -166,7 +178,11 @@ public class CatalogConnectorMetadata {
* @return true if the table exists, false otherwise
*/
public boolean tableExists(String schemaName, String tableName) {
- return tableCatalog.tableExists(NameIdentifier.of(schemaName, tableName));
+ try {
+ return tableCatalog.tableExists(NameIdentifier.of(schemaName,
tableName));
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
+ }
}
/**
@@ -195,6 +211,8 @@ public class CatalogConnectorMetadata {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_TABLE_ALREADY_EXISTS, "Table already
exists", e);
}
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -206,12 +224,14 @@ public class CatalogConnectorMetadata {
public void createSchema(GravitinoSchema schema) {
try {
schemaCatalog.createSchema(schema.getName(), schema.getComment(),
schema.getProperties());
- } catch (NoSuchSchemaException e) {
+ } catch (NoSuchCatalogException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS,
CATALOG_DOES_NOT_EXIST_MSG, e);
- } catch (TableAlreadyExistsException e) {
+ } catch (SchemaAlreadyExistsException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_SCHEMA_ALREADY_EXISTS, "Schema already
exists", e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -222,17 +242,18 @@ public class CatalogConnectorMetadata {
* @param cascade whether to cascade drop the schema
*/
public void dropSchema(String schemaName, boolean cascade) {
+ boolean success;
try {
- boolean success = schemaCatalog.dropSchema(schemaName, cascade);
-
- if (!success) {
- throw new TrinoException(
- GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EXISTS, "Drop schema
failed");
- }
-
+ success = schemaCatalog.dropSchema(schemaName, cascade);
} catch (NonEmptySchemaException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EMPTY, "Schema does not
empty", e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
+ }
+ if (!success) {
+ throw new TrinoException(
+ GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EXISTS, "Drop schema
failed");
}
}
@@ -242,9 +263,14 @@ public class CatalogConnectorMetadata {
* @param tableName the name of the table
*/
public void dropTable(SchemaTableName tableName) {
- boolean dropped =
- tableCatalog.dropTable(
- NameIdentifier.of(tableName.getSchemaName(),
tableName.getTableName()));
+ boolean dropped;
+ try {
+ dropped =
+ tableCatalog.dropTable(
+ NameIdentifier.of(tableName.getSchemaName(),
tableName.getTableName()));
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
+ }
if (!dropped) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_OPERATION_FAILED, "Failed to drop table
" + tableName);
@@ -268,12 +294,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchTableException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_TABLE_NOT_EXISTS, "Table does not
exist");
- } catch (IllegalArgumentException e) {
- // TODO yuhui need improve get the error message. From
IllegalArgumentException.
- // At present, the IllegalArgumentException cannot get the error
information clearly from the
- // Gravitino server.
- String message = GravitinoErrorCode.toSimpleErrorMessage(e);
- throw new TrinoException(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT,
message, e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -436,6 +458,8 @@ public class CatalogConnectorMetadata {
} catch (NoSuchSchemaException e) {
throw new TrinoException(
GravitinoErrorCode.GRAVITINO_SCHEMA_NOT_EXISTS,
SCHEMA_DOES_NOT_EXIST_MSG, e);
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
}
}
@@ -446,11 +470,19 @@ public class CatalogConnectorMetadata {
* @param functionName the name of the function
* @return the function
* @throws UnsupportedOperationException if the catalog does not support
functions
+ * @throws NoSuchFunctionException if the function does not exist
*/
public Function getFunction(String schemaName, String functionName) {
if (!supportsFunctions()) {
throw new UnsupportedOperationException("Catalog does not support
functions");
}
- return functionCatalog.getFunction(NameIdentifier.of(schemaName,
functionName));
+ try {
+ return functionCatalog.getFunction(NameIdentifier.of(schemaName,
functionName));
+ } catch (NoSuchFunctionException e) {
+ // Callers treat a missing function as an empty result.
+ throw e;
+ } catch (RuntimeException e) {
+ throw GravitinoErrorCode.toTrinoException(e);
+ }
}
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/AlterCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/AlterCatalogStoredProcedure.java
index f0dcf7cf0e..18d9c79ebe 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/AlterCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/AlterCatalogStoredProcedure.java
@@ -34,7 +34,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
-import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.CatalogChange;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.exceptions.NoSuchCatalogException;
@@ -173,10 +172,8 @@ public class AlterCatalogStoredProcedure extends
GravitinoStoredProcedure {
GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS,
"Catalog " + NameIdentifier.of(metalake, catalogName) + " not
exists.");
} catch (Exception e) {
- throw new TrinoException(
- GravitinoErrorCode.GRAVITINO_UNSUPPORTED_OPERATION,
- "alter catalog failed. " + (StringUtils.isEmpty(e.getMessage()) ? ""
: e.getMessage()),
- e);
+ throw GravitinoErrorCode.toTrinoException(
+ e, "Alter catalog " + NameIdentifier.of(metalake, catalogName) + "
failed");
}
}
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/CreateCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/CreateCatalogStoredProcedure.java
index 405ef0ce73..74d26c6b50 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/CreateCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/CreateCatalogStoredProcedure.java
@@ -101,7 +101,8 @@ public class CreateCatalogStoredProcedure extends
GravitinoStoredProcedure {
* @param properties the properties of the catalog
* @param ignoreExist whether to ignore if the catalog already exists
* @param metalakeArgument the metalake to create the catalog in, null to
use the configured one
- * @throws TrinoException if the catalog already exists and ignoreExist is
false
+ * @throws TrinoException if the catalog already exists and ignoreExist is
false, or the operation
+ * fails
*/
public void createCatalog(
String catalogName,
@@ -147,13 +148,14 @@ public class CreateCatalogStoredProcedure extends
GravitinoStoredProcedure {
"Catalog " + NameIdentifier.of(metalake, catalogName) + " already
exists in the server.");
} catch (Exception e) {
// The failure can come from a CREATE CATALOG the load loop ran, whose
message may carry the
- // credentials the statement embedded, so it is masked before reaching
the caller.
+ // credentials the statement embedded, so the message is masked while
the classified error
+ // code is kept.
+ TrinoException error =
+ GravitinoErrorCode.toTrinoException(
+ e, "Create catalog " + NameIdentifier.of(metalake, catalogName)
+ " failed");
throw new TrinoException(
- GravitinoErrorCode.GRAVITINO_UNSUPPORTED_OPERATION,
- "Create catalog failed. "
- + (StringUtils.isEmpty(e.getMessage())
- ? ""
- : CatalogRegister.redactSecrets(e.getMessage())),
+ error::getErrorCode,
+
CatalogRegister.redactSecrets(StringUtils.defaultString(error.getMessage())),
e);
}
}
diff --git
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/DropCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/DropCatalogStoredProcedure.java
index a813a35df1..6d2fc9234a 100644
---
a/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/DropCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/main/java/org/apache/gravitino/trino/connector/system/storedprocedure/DropCatalogStoredProcedure.java
@@ -28,7 +28,6 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.List;
import javax.annotation.Nullable;
-import org.apache.commons.lang3.StringUtils;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
@@ -88,7 +87,8 @@ public class DropCatalogStoredProcedure extends
GravitinoStoredProcedure {
* @param catalogName the name of the catalog to drop
* @param ignoreNotExist whether to ignore if the catalog does not exist
* @param metalakeArgument the metalake to drop the catalog from, null to
use the configured one
- * @throws TrinoException if the catalog does not exist and ignoreNotExist
is false
+ * @throws TrinoException if the catalog does not exist and ignoreNotExist
is false, or the
+ * operation fails
*/
public void dropCatalog(
String catalogName, boolean ignoreNotExist, @Nullable String
metalakeArgument) {
@@ -144,10 +144,8 @@ public class DropCatalogStoredProcedure extends
GravitinoStoredProcedure {
GravitinoErrorCode.GRAVITINO_METALAKE_NOT_EXISTS,
"Metalake " + metalake + " not exists.");
} catch (Exception e) {
- throw new TrinoException(
- GravitinoErrorCode.GRAVITINO_UNSUPPORTED_OPERATION,
- "Drop catalog failed. " + (StringUtils.isEmpty(e.getMessage()) ? ""
: e.getMessage()),
- e);
+ throw GravitinoErrorCode.toTrinoException(
+ e, "Drop catalog " + NameIdentifier.of(metalake, catalogName) + "
failed");
}
}
}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
index b1d9465ba1..a04c172d88 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnector.java
@@ -285,8 +285,7 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
+ "catalog=>'memory1', provider=>'memory', properties =>
Map(array['trino.bypass.unknown-direct-key'], array['10']))",
// The message must carry the reason the registration failed, not just
the fact that it
// did. (?s) lets .* span the newlines of the underlying configuration
error.
- "(?s)Create catalog failed. Create catalog failed due to the loading
process fails\\."
- + ".*unknown-direct-key.*");
+ "(?s)Create catalog failed due to the loading process fails\\." +
".*unknown-direct-key.*");
assertThat(computeActual("show
catalogs").getOnlyColumnAsSet()).doesNotContain("memory1");
// Failed registration still leaves a Gravitino catalog entry visible to
catalog_status /
// system.catalog; drop it so later tests that assume only the seed
"memory" catalog stay
@@ -375,7 +374,7 @@ public abstract class TestGravitinoConnector extends
AbstractGravitinoConnectorT
"call gravitino.system.create_catalog("
+ "catalog=>'memory_failed', provider=>'memory',"
+ " properties => Map(array['trino.bypass.unknown-direct-key'],
array['10']))",
- "(?s)Create catalog failed. Create catalog failed due to the loading
process fails\\..*");
+ "(?s)Create catalog failed due to the loading process fails\\..*");
assertThat(computeActual("show catalogs").getOnlyColumnAsSet())
.doesNotContain("memory_failed");
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorForwardUser.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorForwardUser.java
index 82e73441e2..d161d00250 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorForwardUser.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoConnectorForwardUser.java
@@ -47,6 +47,7 @@ import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsSchemas;
import org.apache.gravitino.client.GravitinoAdminClient;
import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.rel.TableCatalog;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorContext;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorMetadata;
@@ -320,6 +321,29 @@ class TestGravitinoConnectorForwardUser {
TrinoException.class,
() -> connector.resolveSessionMetadata(mockSession("alice",
"token-a")));
assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), ex.getErrorCode());
+ assertEquals(
+ "Failed to create per-user Gravitino client for user 'alice':
connection refused",
+ ex.getMessage());
+ }
+
+ @Test
+ void testResolveSessionMetadataMapsLoadMetalakeDenialToPermissionDenied() {
+ CatalogConnectorContext ctx =
+ mockContextWithConfig(
+ ImmutableMap.of(
+ GravitinoAuthProvider.FORWARD_SESSION_USER_KEY, "true",
+ GravitinoAuthProvider.AUTH_TYPE_KEY, "oauth2"));
+ String message = "User alice is not authorized to perform operation
loadMetalake";
+ GravitinoAdminClient client = mock(GravitinoAdminClient.class);
+ when(client.loadMetalake(any())).thenThrow(new
ForbiddenException(message));
+ GravitinoConnector connector = newConnectorWithAuthClient(ctx, session ->
client);
+
+ TrinoException ex =
+ assertThrows(
+ TrinoException.class,
+ () -> connector.resolveSessionMetadata(mockSession("alice",
"token-a")));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), ex.getErrorCode());
+ assertEquals(message, ex.getMessage());
}
/**
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoErrorCode.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoErrorCode.java
new file mode 100644
index 0000000000..0306efb637
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/TestGravitinoErrorCode.java
@@ -0,0 +1,167 @@
+/*
+ * 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.gravitino.trino.connector;
+
+import static io.trino.spi.StandardErrorCode.CATALOG_UNAVAILABLE;
+import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+import io.trino.spi.TrinoException;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.util.concurrent.ExecutionException;
+import org.apache.gravitino.exceptions.ConnectionFailedException;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.MetalakeNotInUseException;
+import org.apache.gravitino.exceptions.RESTException;
+import org.junit.jupiter.api.Test;
+
+public class TestGravitinoErrorCode {
+
+ @Test
+ public void testTrinoExceptionIsReturnedAsIs() {
+ TrinoException original =
+ new TrinoException(GravitinoErrorCode.GRAVITINO_TABLE_NOT_EXISTS,
"missing");
+ assertSame(original, GravitinoErrorCode.toTrinoException(original));
+ }
+
+ @Test
+ public void testForbiddenKeepsFirstLine() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(
+ new ForbiddenException("denied\n\tat
org.apache.gravitino.Server"));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ assertEquals("denied", e.getMessage());
+ }
+
+ @Test
+ public void testNotInUseIsCatalogUnavailable() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(
+ new MetalakeNotInUseException("Metalake m is not in use"));
+ assertEquals(CATALOG_UNAVAILABLE.toErrorCode(), e.getErrorCode());
+ assertEquals("Metalake m is not in use", e.getMessage());
+ }
+
+ @Test
+ public void testIllegalArgumentIsIllegalArgument() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(new
IllegalArgumentException("Illegal name: 9lives"));
+ assertEquals(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testUnsupportedOperationIsUnsupportedOperation() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(new
UnsupportedOperationException("not supported"));
+ assertEquals(
+ GravitinoErrorCode.GRAVITINO_UNSUPPORTED_OPERATION.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testSourceConnectionFailureIsConnectionFailed() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(
+ new ConnectionFailedException("Failed to connect to Hive
Metastore"));
+ assertEquals(GravitinoErrorCode.GRAVITINO_CONNECTION_FAILED.toErrorCode(),
e.getErrorCode());
+ assertEquals("Failed to connect to Hive Metastore", e.getMessage());
+ }
+
+ @Test
+ public void testUnreachableServerIsConnectionFailed() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(
+ new RESTException(
+ new ConnectException("Connection refused"),
+ "Failed to connect to Gravitino server"));
+ assertEquals(GravitinoErrorCode.GRAVITINO_CONNECTION_FAILED.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testUnexpectedServerResponseIsInternalError() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(new RESTException("Unhandled
error: %s", "code 9999"));
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), e.getErrorCode());
+
+ e =
+ GravitinoErrorCode.toTrinoException(
+ new RESTException(new IOException("Unexpected token"), "Failed to
parse response"));
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), e.getErrorCode());
+ }
+
+ @Test
+ public void testUnclassifiedFailureIsInternalError() {
+ TrinoException e = GravitinoErrorCode.toTrinoException(new
RuntimeException("boom"));
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), e.getErrorCode());
+ assertEquals("boom", e.getMessage());
+ }
+
+ @Test
+ public void testExecutionExceptionIsClassifiedByCause() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(
+ new ExecutionException(new ForbiddenException("denied")));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ assertEquals("denied", e.getMessage());
+ }
+
+ @Test
+ public void testContextPrefixesOnlyUnclassifiedFailures() {
+ TrinoException internal =
+ GravitinoErrorCode.toTrinoException(
+ new RuntimeException("boom"), "Drop catalog m.c failed");
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(),
internal.getErrorCode());
+ assertEquals("Drop catalog m.c failed: boom", internal.getMessage());
+
+ TrinoException denied =
+ GravitinoErrorCode.toTrinoException(
+ new ForbiddenException("denied"), "Drop catalog m.c failed");
+ assertEquals("denied", denied.getMessage());
+ }
+
+ @Test
+ public void testEmptyMessageFallsBackToExceptionName() {
+ TrinoException e = GravitinoErrorCode.toTrinoException(new
IllegalArgumentException(""));
+ assertEquals(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(),
e.getErrorCode());
+ assertEquals("java.lang.IllegalArgumentException: ", e.getMessage());
+ }
+
+ @Test
+ public void testServerStackTraceIsDroppedFromMessage() {
+ String stack = "\n\tat
org.apache.gravitino.server.web.rest.TableOperations.createTable";
+ TrinoException illegal =
+ GravitinoErrorCode.toTrinoException(
+ new IllegalArgumentException("Illegal name: 9lives" + stack));
+ assertEquals("Illegal name: 9lives", illegal.getMessage());
+
+ TrinoException internal =
+ GravitinoErrorCode.toTrinoException(
+ new RuntimeException("boom" + stack), "Drop catalog m.c failed");
+ assertEquals("Drop catalog m.c failed: boom", internal.getMessage());
+ }
+
+ @Test
+ public void testLeadingBlankLinesAreSkipped() {
+ TrinoException e =
+ GravitinoErrorCode.toTrinoException(new RuntimeException("\n
\nboom\nmore"), "Op failed");
+ assertEquals("Op failed: boom", e.getMessage());
+ }
+}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataErrors.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataErrors.java
new file mode 100644
index 0000000000..a5742203df
--- /dev/null
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/catalog/TestCatalogConnectorMetadataErrors.java
@@ -0,0 +1,223 @@
+/*
+ * 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.gravitino.trino.connector.catalog;
+
+import static io.trino.spi.StandardErrorCode.CATALOG_UNAVAILABLE;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import io.trino.spi.TrinoException;
+import io.trino.spi.connector.SchemaTableName;
+import org.apache.gravitino.Catalog;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.SupportsSchemas;
+import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.CatalogNotInUseException;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.IllegalNameIdentifierException;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchTableException;
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
+import org.apache.gravitino.rel.TableCatalog;
+import org.apache.gravitino.trino.connector.GravitinoErrorCode;
+import org.apache.gravitino.trino.connector.metadata.GravitinoSchema;
+import org.apache.gravitino.trino.connector.metadata.GravitinoTable;
+import org.junit.jupiter.api.Test;
+
+public class TestCatalogConnectorMetadataErrors {
+
+ private static final String DENIED_MESSAGE =
+ "User joel is not authorized to perform operation loadTable on metadata
m.c.s.t";
+
+ @Test
+ public void testLoadCatalogDeniedIsPermissionDenied() {
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(metalake.loadCatalog(anyString())).thenThrow(new
ForbiddenException(DENIED_MESSAGE));
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () -> new CatalogConnectorMetadata(metalake,
NameIdentifier.of("m", "c")));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ assertEquals(DENIED_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ public void testGetTableDeniedIsPermissionDenied() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.loadTable(any())).thenThrow(new
ForbiddenException(DENIED_MESSAGE));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.getTable("s", "t"));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ assertEquals(DENIED_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ public void testDeniedMessageWithServerStackKeepsFirstLine() {
+ // The REST client appends the server stack trace to the message of a
refusal.
+ String messageWithStack =
+ DENIED_MESSAGE
+ + "\n"
+ + "org.apache.gravitino.exceptions.ForbiddenException: "
+ + DENIED_MESSAGE
+ + "\n"
+ + "\tat
org.apache.gravitino.server.web.rest.TableOperations.loadTable(TableOperations.java:1)";
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.loadTable(any())).thenThrow(new
ForbiddenException(messageWithStack));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.getTable("s", "t"));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ assertEquals(DENIED_MESSAGE, e.getMessage());
+ }
+
+ @Test
+ public void testIllegalArgumentWithoutMessageIsIllegalArgument() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.loadTable(any())).thenThrow(new
IllegalNameIdentifierException());
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.getTable("s", "t"));
+ assertEquals(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testGetMissingTableKeepsTableNotExists() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.loadTable(any())).thenThrow(new
NoSuchTableException("missing"));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.getTable("s", "t"));
+ assertEquals(GravitinoErrorCode.GRAVITINO_TABLE_NOT_EXISTS.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testTableExistsDeniedIsPermissionDenied() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.tableExists(any())).thenThrow(new
ForbiddenException(DENIED_MESSAGE));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.tableExists("s", "t"));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ }
+
+ @Test
+ public void testCreateTableWithIllegalNameIsIllegalArgument() {
+ String message = "The TABLE name 9lives is illegal. Illegal name: 9lives";
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.createTable(any(), any(), any(), any(), any(), any(),
any(), any()))
+ .thenThrow(new IllegalArgumentException(message));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+ GravitinoTable table = mock(GravitinoTable.class);
+ when(table.getSchemaName()).thenReturn("s");
+ when(table.getName()).thenReturn("9lives");
+
+ TrinoException e = assertThrows(TrinoException.class, () ->
metadata.createTable(table, false));
+ assertEquals(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(),
e.getErrorCode());
+ assertEquals(message, e.getMessage());
+ }
+
+ @Test
+ public void testDropTableDeniedIsPermissionDenied() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.dropTable(any())).thenThrow(new
ForbiddenException(DENIED_MESSAGE));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e =
+ assertThrows(TrinoException.class, () -> metadata.dropTable(new
SchemaTableName("s", "t")));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ }
+
+ @Test
+ public void testAlterTableDeniedIsPermissionDenied() {
+ TableCatalog tableCatalog = mock(TableCatalog.class);
+ when(tableCatalog.alterTable(any(), any())).thenThrow(new
ForbiddenException(DENIED_MESSAGE));
+ CatalogConnectorMetadata metadata = createMetadata(tableCatalog);
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () ->
+ metadata.renameTable(
+ new SchemaTableName("s", "t"), new SchemaTableName("s",
"t2")));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), e.getErrorCode());
+ }
+
+ @Test
+ public void testLoadCatalogNotInUseIsCatalogUnavailable() {
+ String message = "Catalog m.c is not in use";
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(metalake.loadCatalog(anyString())).thenThrow(new
CatalogNotInUseException(message));
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class,
+ () -> new CatalogConnectorMetadata(metalake,
NameIdentifier.of("m", "c")));
+ assertEquals(CATALOG_UNAVAILABLE.toErrorCode(), e.getErrorCode());
+ assertEquals(message, e.getMessage());
+ }
+
+ @Test
+ public void testCreateExistingSchemaIsSchemaAlreadyExists() {
+ SupportsSchemas schemas = mock(SupportsSchemas.class);
+ when(schemas.createSchema(any(), any(), any()))
+ .thenThrow(new SchemaAlreadyExistsException("exists"));
+ CatalogConnectorMetadata metadata = createMetadata(schemas,
mock(TableCatalog.class));
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class, () ->
metadata.createSchema(mock(GravitinoSchema.class)));
+ assertEquals(
+ GravitinoErrorCode.GRAVITINO_SCHEMA_ALREADY_EXISTS.toErrorCode(),
e.getErrorCode());
+ }
+
+ @Test
+ public void testCreateSchemaInMissingCatalogIsCatalogNotExists() {
+ SupportsSchemas schemas = mock(SupportsSchemas.class);
+ when(schemas.createSchema(any(), any(), any()))
+ .thenThrow(new NoSuchCatalogException("missing"));
+ CatalogConnectorMetadata metadata = createMetadata(schemas,
mock(TableCatalog.class));
+
+ TrinoException e =
+ assertThrows(
+ TrinoException.class, () ->
metadata.createSchema(mock(GravitinoSchema.class)));
+
assertEquals(GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS.toErrorCode(),
e.getErrorCode());
+ }
+
+ private static CatalogConnectorMetadata createMetadata(TableCatalog
tableCatalog) {
+ return createMetadata(mock(SupportsSchemas.class), tableCatalog);
+ }
+
+ private static CatalogConnectorMetadata createMetadata(
+ SupportsSchemas schemas, TableCatalog tableCatalog) {
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ Catalog catalog = mock(Catalog.class);
+ when(metalake.loadCatalog(anyString())).thenReturn(catalog);
+ when(catalog.asSchemas()).thenReturn(schemas);
+ when(catalog.asTableCatalog()).thenReturn(tableCatalog);
+ when(catalog.asFunctionCatalog()).thenThrow(new
UnsupportedOperationException("unsupported"));
+ return new CatalogConnectorMetadata(metalake, NameIdentifier.of("m", "c"));
+ }
+}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestAlterCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestAlterCatalogStoredProcedure.java
index 5ec6fe7133..72f8e4347d 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestAlterCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestAlterCatalogStoredProcedure.java
@@ -18,6 +18,7 @@
*/
package org.apache.gravitino.trino.connector.system.storedprocedure;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -35,6 +36,7 @@ import com.google.common.collect.ImmutableMap;
import io.trino.spi.TrinoException;
import java.util.List;
import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorContext;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorManager;
@@ -102,7 +104,31 @@ public class TestAlterCatalogStoredProcedure {
assertThrows(
TrinoException.class,
() -> procedure.alterCatalog(CATALOG, ImmutableMap.of("k", "v"),
List.of(), null));
+ assertEquals(
+ GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS.toErrorCode(),
error.getErrorCode());
assertTrue(error.getMessage().contains("already registered"),
error.getMessage());
verify(otherMetalake, never()).alterCatalog(anyString(), any());
}
+
+ @Test
+ public void testAlterCatalogDeniedIsPermissionDenied() {
+ String message = "User joel is not authorized to perform operation
alterCatalog";
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+ CatalogConnectorContext context = mock(CatalogConnectorContext.class);
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(manager.getCatalogConnector(METALAKE, CATALOG)).thenReturn(context);
+ when(context.getMetalake()).thenReturn(metalake);
+ when(context.getCatalog())
+ .thenReturn(new GravitinoCatalog(METALAKE, "memory", CATALOG,
ImmutableMap.of(), 1L));
+ when(metalake.alterCatalog(eq(CATALOG), any())).thenThrow(new
ForbiddenException(message));
+
+ AlterCatalogStoredProcedure procedure = new
AlterCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(
+ TrinoException.class,
+ () -> procedure.alterCatalog(CATALOG, ImmutableMap.of("k", "v"),
List.of(), null));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), error.getErrorCode());
+ assertEquals(message, error.getMessage());
+ }
}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestCreateCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestCreateCatalogStoredProcedure.java
index 9a451f386a..16b61aa667 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestCreateCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestCreateCatalogStoredProcedure.java
@@ -18,13 +18,17 @@
*/
package org.apache.gravitino.trino.connector.system.storedprocedure;
+import static io.trino.spi.StandardErrorCode.GENERIC_INTERNAL_ERROR;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
@@ -34,8 +38,10 @@ import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableMap;
import io.trino.spi.TrinoException;
import java.util.Map;
+import java.util.concurrent.TimeoutException;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorContext;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorManager;
@@ -103,6 +109,85 @@ public class TestCreateCatalogStoredProcedure {
TrinoException.class,
() -> procedure.createCatalog(CATALOG, "memory", PROPERTIES,
false, null));
verify(requested, times(1)).createCatalog(eq(CATALOG), any(),
eq("memory"), any(), any());
+ assertEquals(GravitinoErrorCode.GRAVITINO_OPERATION_FAILED.toErrorCode(),
error.getErrorCode());
assertTrue(error.getMessage().contains("already registered"),
error.getMessage());
}
+
+ @Test
+ public void testCreateCatalogDeniedIsPermissionDenied() {
+ String message = "User joel is not authorized to perform operation
createCatalog";
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(manager.getMetalake(METALAKE)).thenReturn(metalake);
+ when(metalake.createCatalog(anyString(), any(), anyString(), anyString(),
any()))
+ .thenThrow(new ForbiddenException(message));
+
+ CreateCatalogStoredProcedure procedure = new
CreateCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(
+ TrinoException.class,
+ () -> procedure.createCatalog(CATALOG, "memory", PROPERTIES,
false, null));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), error.getErrorCode());
+ assertEquals(message, error.getMessage());
+ }
+
+ @Test
+ public void testCreateCatalogIllegalArgumentIsIllegalArgument() {
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(manager.getMetalake(METALAKE)).thenReturn(metalake);
+ when(metalake.createCatalog(anyString(), any(), anyString(), anyString(),
any()))
+ .thenThrow(new IllegalArgumentException("Unsupported provider: bad"));
+
+ CreateCatalogStoredProcedure procedure = new
CreateCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(
+ TrinoException.class,
+ () -> procedure.createCatalog(CATALOG, "bad", PROPERTIES, false,
null));
+ assertEquals(GravitinoErrorCode.GRAVITINO_ILLEGAL_ARGUMENT.toErrorCode(),
error.getErrorCode());
+ assertTrue(error.getMessage().contains("Unsupported provider: bad"),
error.getMessage());
+ }
+
+ @Test
+ public void
testCreateCatalogUnclassifiedFailureKeepsContextAndRedactsSecrets() throws
Exception {
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+
when(manager.getMetalake(METALAKE)).thenReturn(mock(GravitinoMetalake.class));
+ doThrow(
+ new RuntimeException(
+ "CREATE CATALOG c USING jdbc WITH
(\"trino.jdbc.password\"='s3cret') failed"))
+ .when(manager)
+ .loadMetalakeSync();
+
+ CreateCatalogStoredProcedure procedure = new
CreateCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(
+ TrinoException.class,
+ () -> procedure.createCatalog(CATALOG, "memory", PROPERTIES,
false, null));
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), error.getErrorCode());
+ assertTrue(
+ error.getMessage().startsWith("Create catalog
test_metalake.test_catalog failed: "),
+ error.getMessage());
+ assertFalse(error.getMessage().contains("s3cret"), error.getMessage());
+ }
+
+ @Test
+ public void testCreateCatalogReloadTimeoutKeepsContext() throws Exception {
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+
when(manager.getMetalake(METALAKE)).thenReturn(mock(GravitinoMetalake.class));
+ doThrow(new TimeoutException()).when(manager).loadMetalakeSync();
+
+ CreateCatalogStoredProcedure procedure = new
CreateCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(
+ TrinoException.class,
+ () -> procedure.createCatalog(CATALOG, "memory", PROPERTIES,
false, null));
+ assertEquals(GENERIC_INTERNAL_ERROR.toErrorCode(), error.getErrorCode());
+ assertEquals(
+ "Create catalog test_metalake.test_catalog failed:
java.util.concurrent.TimeoutException",
+ error.getMessage());
+ }
}
diff --git
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestDropCatalogStoredProcedure.java
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestDropCatalogStoredProcedure.java
index cffd2d5a9f..72310271f8 100644
---
a/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestDropCatalogStoredProcedure.java
+++
b/trino-connector/trino-connector/src/test/java/org/apache/gravitino/trino/connector/system/storedprocedure/TestDropCatalogStoredProcedure.java
@@ -18,6 +18,7 @@
*/
package org.apache.gravitino.trino.connector.system.storedprocedure;
+import static io.trino.spi.StandardErrorCode.PERMISSION_DENIED;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -33,6 +34,7 @@ import static org.mockito.Mockito.when;
import io.trino.spi.TrinoException;
import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.ForbiddenException;
import org.apache.gravitino.trino.connector.GravitinoErrorCode;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorContext;
import org.apache.gravitino.trino.connector.catalog.CatalogConnectorManager;
@@ -135,22 +137,13 @@ public class TestDropCatalogStoredProcedure {
DropCatalogStoredProcedure procedure = new
DropCatalogStoredProcedure(manager, METALAKE);
- // The internally-thrown "not exists" TrinoException is caught by the
outer catch(Exception)
- // and re-wrapped as GRAVITINO_UNSUPPORTED_OPERATION, with the original
exception preserved
- // as the cause. This matches the historical behavior observed in the
issue's stack trace.
- // The contract we verify is: the user-visible message still tells them
the catalog does not
- // exist, and the root cause carries the GRAVITINO_CATALOG_NOT_EXISTS
error code.
TrinoException error =
assertThrows(TrinoException.class, () ->
procedure.dropCatalog(CATALOG, false, null));
assertTrue(
error.getMessage().contains("not exists"),
() -> "Expected message to contain 'not exists' but was: " +
error.getMessage());
- Throwable cause = error.getCause();
- assertTrue(
- cause instanceof TrinoException, () -> "Expected TrinoException cause,
was: " + cause);
assertEquals(
- GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS.toErrorCode(),
- ((TrinoException) cause).getErrorCode());
+ GravitinoErrorCode.GRAVITINO_CATALOG_NOT_EXISTS.toErrorCode(),
error.getErrorCode());
}
@Test
@@ -173,4 +166,20 @@ public class TestDropCatalogStoredProcedure {
// Local-cache reload should NOT be triggered when the local connector was
already absent.
verify(manager, never()).catalogConnectorExist(anyString());
}
+
+ @Test
+ public void testDropCatalogDeniedIsPermissionDenied() {
+ String message = "User joel is not authorized to perform operation
dropCatalog";
+ CatalogConnectorManager manager = mock(CatalogConnectorManager.class);
+ GravitinoMetalake metalake = mock(GravitinoMetalake.class);
+ when(manager.getMetalake(METALAKE)).thenReturn(metalake);
+ when(metalake.dropCatalog(CATALOG, true)).thenThrow(new
ForbiddenException(message));
+
+ DropCatalogStoredProcedure procedure = new
DropCatalogStoredProcedure(manager, METALAKE);
+
+ TrinoException error =
+ assertThrows(TrinoException.class, () ->
procedure.dropCatalog(CATALOG, false, null));
+ assertEquals(PERMISSION_DENIED.toErrorCode(), error.getErrorCode());
+ assertEquals(message, error.getMessage());
+ }
}