From fa528969a2df03a41ef2296f910546b908940f9c Mon Sep 17 00:00:00 2001 From: jerry Date: Thu, 9 Jan 2025 20:06:30 +0800 Subject: [PATCH] [flink] Add Tests and ITCases in flink for RESTCatalog (#4805) --- paimon-core/pom.xml | 1 - .../paimon/catalog/AbstractCatalog.java | 12 +- .../apache/paimon/catalog/CatalogUtils.java | 13 + .../paimon/rest/DefaultErrorHandler.java | 9 +- .../org/apache/paimon/rest/HttpClient.java | 23 +- .../org/apache/paimon/rest/RESTCatalog.java | 57 ++- .../org/apache/paimon/rest/ResourcePaths.java | 54 +-- .../apache/paimon/rest/auth/AuthSession.java | 2 +- .../exceptions/AlreadyExistsException.java | 21 +- .../rest/exceptions/ForbiddenException.java | 1 + .../exceptions/NoSuchResourceException.java | 21 +- .../exceptions/NotAuthorizedException.java | 1 + .../paimon/rest/exceptions/RESTException.java | 1 + .../exceptions/ServiceFailureException.java | 1 + .../ServiceUnavailableException.java | 1 + .../UnsupportedOperationException.java | 27 ++ .../paimon/rest/responses/ErrorResponse.java | 36 +- .../responses/ErrorResponseResourceType.java | 26 ++ .../paimon/catalog/CatalogTestBase.java | 165 +++++--- .../apache/paimon/jdbc/JdbcCatalogTest.java | 6 +- .../paimon/rest/DefaultErrorHandlerTest.java | 4 +- .../apache/paimon/rest/HttpClientTest.java | 51 ++- .../apache/paimon/rest/MockRESTMessage.java | 14 +- .../apache/paimon/rest/RESTCatalogServer.java | 329 +++++++++++++++ .../apache/paimon/rest/RESTCatalogTest.java | 381 ++++-------------- .../paimon/rest/RESTObjectMapperTest.java | 86 ++-- .../paimon/rest/auth/AuthSessionTest.java | 17 + paimon-flink/paimon-flink-common/pom.xml | 7 + .../paimon/flink/CatalogITCaseBase.java | 8 +- .../paimon/flink/RESTCatalogITCase.java | 116 ++++++ .../apache/paimon/hive/HiveCatalogTest.java | 10 +- pom.xml | 1 + 32 files changed, 966 insertions(+), 536 deletions(-) create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/exceptions/UnsupportedOperationException.java create mode 100644 paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponseResourceType.java create mode 100644 paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java create mode 100644 paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/RESTCatalogITCase.java diff --git a/paimon-core/pom.xml b/paimon-core/pom.xml index 6cdb9a9c93e0..c4d8f0283bc6 100644 --- a/paimon-core/pom.xml +++ b/paimon-core/pom.xml @@ -33,7 +33,6 @@ under the License. 6.20.3-ververica-2.0 - 4.12.0 diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java index 2ecbcf61b334..a4c47f54a6ab 100644 --- a/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/catalog/AbstractCatalog.java @@ -63,6 +63,7 @@ import static org.apache.paimon.catalog.CatalogUtils.checkNotSystemTable; import static org.apache.paimon.catalog.CatalogUtils.isSystemDatabase; import static org.apache.paimon.catalog.CatalogUtils.listPartitionsFromFileSystem; +import static org.apache.paimon.catalog.CatalogUtils.validateAutoCreateClose; import static org.apache.paimon.options.CatalogOptions.LOCK_ENABLED; import static org.apache.paimon.options.CatalogOptions.LOCK_TYPE; import static org.apache.paimon.table.system.AllTableOptionsTable.ALL_TABLE_OPTIONS; @@ -504,17 +505,6 @@ private void copyTableDefaultOptions(Map options) { tableDefaultOptions.forEach(options::putIfAbsent); } - private void validateAutoCreateClose(Map options) { - checkArgument( - !Boolean.parseBoolean( - options.getOrDefault( - CoreOptions.AUTO_CREATE.key(), - CoreOptions.AUTO_CREATE.defaultValue().toString())), - String.format( - "The value of %s property should be %s.", - CoreOptions.AUTO_CREATE.key(), Boolean.FALSE)); - } - private void validateCustomTablePath(Map options) { if (!allowCustomTablePath() && options.containsKey(CoreOptions.PATH.key())) { throw new UnsupportedOperationException( diff --git a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java index fabfa50fc4d7..9267532f9d22 100644 --- a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java +++ b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java @@ -18,6 +18,7 @@ package org.apache.paimon.catalog; +import org.apache.paimon.CoreOptions; import org.apache.paimon.fs.Path; import org.apache.paimon.manifest.PartitionEntry; import org.apache.paimon.options.Options; @@ -38,6 +39,7 @@ import static org.apache.paimon.catalog.Catalog.SYSTEM_DATABASE_NAME; import static org.apache.paimon.catalog.Catalog.TABLE_DEFAULT_OPTION_PREFIX; import static org.apache.paimon.options.OptionsUtils.convertToPropertiesPrefixKey; +import static org.apache.paimon.utils.Preconditions.checkArgument; /** Utils for {@link Catalog}. */ public class CatalogUtils { @@ -108,6 +110,17 @@ public static void checkNotBranch(Identifier identifier, String method) { } } + public static void validateAutoCreateClose(Map options) { + checkArgument( + !Boolean.parseBoolean( + options.getOrDefault( + CoreOptions.AUTO_CREATE.key(), + CoreOptions.AUTO_CREATE.defaultValue().toString())), + String.format( + "The value of %s property should be %s.", + CoreOptions.AUTO_CREATE.key(), Boolean.FALSE)); + } + public static Table createSystemTable(Identifier identifier, Table originTable) throws Catalog.TableNotExistException { if (!(originTable instanceof FileStoreTable)) { diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java b/paimon-core/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java index ce2cbb56ae24..944b986b3f1d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/DefaultErrorHandler.java @@ -26,6 +26,7 @@ import org.apache.paimon.rest.exceptions.RESTException; import org.apache.paimon.rest.exceptions.ServiceFailureException; import org.apache.paimon.rest.exceptions.ServiceUnavailableException; +import org.apache.paimon.rest.exceptions.UnsupportedOperationException; import org.apache.paimon.rest.responses.ErrorResponse; /** Default error handler. */ @@ -43,18 +44,20 @@ public void accept(ErrorResponse error) { String message = error.getMessage(); switch (code) { case 400: - throw new BadRequestException(String.format("Malformed request: %s", message)); + throw new BadRequestException(String.format("%s", message)); case 401: throw new NotAuthorizedException("Not authorized: %s", message); case 403: throw new ForbiddenException("Forbidden: %s", message); case 404: - throw new NoSuchResourceException("%s", message); + throw new NoSuchResourceException( + error.getResourceType(), error.getResourceName(), "%s", message); case 405: case 406: break; case 409: - throw new AlreadyExistsException("%s", message); + throw new AlreadyExistsException( + error.getResourceType(), error.getResourceName(), "%s", message); case 500: throw new ServiceFailureException("Server error: %s", message); case 501: diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java index 08284fc454b0..2862e5ef02ed 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/HttpClient.java @@ -63,7 +63,11 @@ public HttpClient(Options options) { } public HttpClient(HttpClientOptions httpClientOptions) { - this.uri = httpClientOptions.uri(); + if (httpClientOptions.uri() != null && httpClientOptions.uri().endsWith("/")) { + this.uri = httpClientOptions.uri().substring(0, httpClientOptions.uri().length() - 1); + } else { + this.uri = httpClientOptions.uri(); + } this.okHttpClient = createHttpClient(httpClientOptions); this.errorHandler = DefaultErrorHandler.getInstance(); } @@ -132,10 +136,19 @@ private T exec(Request request, Class responseType) try (Response response = okHttpClient.newCall(request).execute()) { String responseBodyStr = response.body() != null ? response.body().string() : null; if (!response.isSuccessful()) { - ErrorResponse error = - new ErrorResponse( - responseBodyStr != null ? responseBodyStr : "response body is null", - response.code()); + ErrorResponse error; + try { + error = OBJECT_MAPPER.readValue(responseBodyStr, ErrorResponse.class); + } catch (JsonProcessingException e) { + error = + new ErrorResponse( + null, + null, + responseBodyStr != null + ? responseBodyStr + : "response body is null", + response.code()); + } errorHandler.accept(error); } if (responseType != null && responseBodyStr != null) { diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java index 2c36f75a3713..3f7647ca84af 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java @@ -35,8 +35,10 @@ import org.apache.paimon.partition.Partition; import org.apache.paimon.rest.auth.AuthSession; import org.apache.paimon.rest.exceptions.AlreadyExistsException; +import org.apache.paimon.rest.exceptions.BadRequestException; import org.apache.paimon.rest.exceptions.ForbiddenException; import org.apache.paimon.rest.exceptions.NoSuchResourceException; +import org.apache.paimon.rest.exceptions.ServiceFailureException; import org.apache.paimon.rest.requests.AlterDatabaseRequest; import org.apache.paimon.rest.requests.AlterTableRequest; import org.apache.paimon.rest.requests.CreateDatabaseRequest; @@ -45,6 +47,7 @@ import org.apache.paimon.rest.responses.AlterDatabaseResponse; import org.apache.paimon.rest.responses.ConfigResponse; import org.apache.paimon.rest.responses.CreateDatabaseResponse; +import org.apache.paimon.rest.responses.ErrorResponseResourceType; import org.apache.paimon.rest.responses.GetDatabaseResponse; import org.apache.paimon.rest.responses.GetTableResponse; import org.apache.paimon.rest.responses.ListDatabasesResponse; @@ -75,9 +78,12 @@ import java.util.concurrent.ScheduledExecutorService; import static org.apache.paimon.CoreOptions.METASTORE_PARTITIONED_TABLE; +import static org.apache.paimon.catalog.CatalogUtils.checkNotBranch; import static org.apache.paimon.catalog.CatalogUtils.checkNotSystemDatabase; +import static org.apache.paimon.catalog.CatalogUtils.checkNotSystemTable; import static org.apache.paimon.catalog.CatalogUtils.isSystemDatabase; import static org.apache.paimon.catalog.CatalogUtils.listPartitionsFromFileSystem; +import static org.apache.paimon.catalog.CatalogUtils.validateAutoCreateClose; import static org.apache.paimon.options.CatalogOptions.CASE_SENSITIVE; import static org.apache.paimon.rest.RESTUtil.extractPrefixMap; import static org.apache.paimon.rest.auth.AuthSession.createAuthSession; @@ -209,7 +215,7 @@ public void dropDatabase(String name, boolean ignoreIfNotExists, boolean cascade throw new DatabaseNotEmptyException(name); } client.delete(resourcePaths.database(name), headers()); - } catch (NoSuchResourceException e) { + } catch (NoSuchResourceException | DatabaseNotExistException e) { if (!ignoreIfNotExists) { throw new DatabaseNotExistException(name); } @@ -249,12 +255,19 @@ public void alterDatabase(String name, List changes, boolean ign @Override public List listTables(String databaseName) throws DatabaseNotExistException { - ListTablesResponse response = - client.get(resourcePaths.tables(databaseName), ListTablesResponse.class, headers()); - if (response.getTables() != null) { - return response.getTables(); + try { + ListTablesResponse response = + client.get( + resourcePaths.tables(databaseName), + ListTablesResponse.class, + headers()); + if (response.getTables() != null) { + return response.getTables(); + } + return ImmutableList.of(); + } catch (NoSuchResourceException e) { + throw new DatabaseNotExistException(databaseName); } - return ImmutableList.of(); } @Override @@ -272,6 +285,9 @@ public Table getTable(Identifier identifier) throws TableNotExistException { public void createTable(Identifier identifier, Schema schema, boolean ignoreIfExists) throws TableAlreadyExistException, DatabaseNotExistException { try { + checkNotBranch(identifier, "createTable"); + checkNotSystemTable(identifier, "createTable"); + validateAutoCreateClose(schema.options()); CreateTableRequest request = new CreateTableRequest(identifier, schema); client.post( resourcePaths.tables(identifier.getDatabaseName()), @@ -282,12 +298,24 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx if (!ignoreIfExists) { throw new TableAlreadyExistException(identifier); } + } catch (NoSuchResourceException e) { + throw new DatabaseNotExistException(identifier.getDatabaseName()); + } catch (BadRequestException e) { + throw new RuntimeException(new IllegalArgumentException(e.getMessage())); + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); } } @Override public void renameTable(Identifier fromTable, Identifier toTable, boolean ignoreIfNotExists) throws TableNotExistException, TableAlreadyExistException { + checkNotBranch(fromTable, "renameTable"); + checkNotBranch(toTable, "renameTable"); + checkNotSystemTable(fromTable, "renameTable"); + checkNotSystemTable(toTable, "renameTable"); try { RenameTableRequest request = new RenameTableRequest(toTable); client.post( @@ -311,6 +339,7 @@ public void renameTable(Identifier fromTable, Identifier toTable, boolean ignore public void alterTable( Identifier identifier, List changes, boolean ignoreIfNotExists) throws TableNotExistException, ColumnAlreadyExistException, ColumnNotExistException { + checkNotSystemTable(identifier, "alterTable"); try { AlterTableRequest request = new AlterTableRequest(changes); client.post( @@ -320,16 +349,30 @@ public void alterTable( headers()); } catch (NoSuchResourceException e) { if (!ignoreIfNotExists) { - throw new TableNotExistException(identifier); + if (e.resourceType() == ErrorResponseResourceType.TABLE) { + throw new TableNotExistException(identifier); + } else if (e.resourceType() == ErrorResponseResourceType.COLUMN) { + throw new ColumnNotExistException(identifier, e.resourceName()); + } } + } catch (AlreadyExistsException e) { + throw new ColumnAlreadyExistException(identifier, e.resourceName()); } catch (ForbiddenException e) { throw new TableNoPermissionException(identifier, e); + } catch (org.apache.paimon.rest.exceptions.UnsupportedOperationException e) { + throw new UnsupportedOperationException(e.getMessage()); + } catch (ServiceFailureException e) { + throw new IllegalStateException(e.getMessage()); + } catch (BadRequestException e) { + throw new RuntimeException(new IllegalArgumentException(e.getMessage())); } } @Override public void dropTable(Identifier identifier, boolean ignoreIfNotExists) throws TableNotExistException { + checkNotBranch(identifier, "dropTable"); + checkNotSystemTable(identifier, "dropTable"); try { client.delete( resourcePaths.table(identifier.getDatabaseName(), identifier.getTableName()), diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java index 780582c33cb7..f7d2f7116930 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/ResourcePaths.java @@ -20,13 +20,17 @@ import org.apache.paimon.options.Options; -import java.util.StringJoiner; +import org.apache.paimon.shade.guava30.com.google.common.base.Joiner; /** Resource paths for REST catalog. */ public class ResourcePaths { - public static final String V1_CONFIG = "/v1/config"; - private static final StringJoiner SLASH = new StringJoiner("/"); + private static final Joiner SLASH = Joiner.on("/").skipNulls(); + private static final String V1 = "/v1"; + private static final String DATABASES = "databases"; + private static final String TABLES = "tables"; + + public static final String V1_CONFIG = V1 + "/config"; public static ResourcePaths forCatalogProperties(Options options) { return new ResourcePaths(options.get(RESTCatalogInternalOptions.PREFIX)); @@ -39,60 +43,30 @@ public ResourcePaths(String prefix) { } public String databases() { - return SLASH.add("v1").add(prefix).add("databases").toString(); + return SLASH.join(V1, prefix, DATABASES); } public String database(String databaseName) { - return SLASH.add("v1").add(prefix).add("databases").add(databaseName).toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName); } public String databaseProperties(String databaseName) { - return SLASH.add("v1") - .add(prefix) - .add("databases") - .add(databaseName) - .add("properties") - .toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName, "properties"); } public String tables(String databaseName) { - return SLASH.add("v1") - .add(prefix) - .add("databases") - .add(databaseName) - .add("tables") - .toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES); } public String table(String databaseName, String tableName) { - return SLASH.add("v1") - .add(prefix) - .add("databases") - .add(databaseName) - .add("tables") - .add(tableName) - .toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES, tableName); } public String renameTable(String databaseName, String tableName) { - return SLASH.add("v1") - .add(prefix) - .add("databases") - .add(databaseName) - .add("tables") - .add(tableName) - .add("rename") - .toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES, tableName, "rename"); } public String partitions(String databaseName, String tableName) { - return SLASH.add("v1") - .add(prefix) - .add("databases") - .add(databaseName) - .add("tables") - .add(tableName) - .add("partitions") - .toString(); + return SLASH.join(V1, prefix, DATABASES, databaseName, TABLES, tableName, "partitions"); } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java index 198b098687d4..470af7f6f699 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/AuthSession.java @@ -46,8 +46,8 @@ public class AuthSession { private volatile Map headers; public AuthSession(Map headers, CredentialsProvider credentialsProvider) { - this.headers = headers; this.credentialsProvider = credentialsProvider; + this.headers = RESTUtil.merge(headers, this.credentialsProvider.authHeader()); } public static AuthSession fromRefreshCredentialsProvider( diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/AlreadyExistsException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/AlreadyExistsException.java index 8e30c8375bf9..6da7a492b6ed 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/AlreadyExistsException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/AlreadyExistsException.java @@ -18,10 +18,29 @@ package org.apache.paimon.rest.exceptions; +import org.apache.paimon.rest.responses.ErrorResponseResourceType; + /** Exception thrown on HTTP 409 means a resource already exists. */ public class AlreadyExistsException extends RESTException { - public AlreadyExistsException(String message, Object... args) { + private final ErrorResponseResourceType resourceType; + private final String resourceName; + + public AlreadyExistsException( + ErrorResponseResourceType resourceType, + String resourceName, + String message, + Object... args) { super(message, args); + this.resourceType = resourceType; + this.resourceName = resourceName; + } + + public ErrorResponseResourceType resourceType() { + return resourceType; + } + + public String resourceName() { + return resourceName; } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ForbiddenException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ForbiddenException.java index 3982e5b70417..76cb53bfc313 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ForbiddenException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ForbiddenException.java @@ -20,6 +20,7 @@ /** Exception thrown on HTTP 403 Forbidden. */ public class ForbiddenException extends RESTException { + public ForbiddenException(String message, Object... args) { super(message, args); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NoSuchResourceException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NoSuchResourceException.java index cc4c7881f465..6dfb12567151 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NoSuchResourceException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NoSuchResourceException.java @@ -18,10 +18,29 @@ package org.apache.paimon.rest.exceptions; +import org.apache.paimon.rest.responses.ErrorResponseResourceType; + /** Exception thrown on HTTP 404 means a resource not exists. */ public class NoSuchResourceException extends RESTException { - public NoSuchResourceException(String message, Object... args) { + private final ErrorResponseResourceType resourceType; + private final String resourceName; + + public NoSuchResourceException( + ErrorResponseResourceType resourceType, + String resourceName, + String message, + Object... args) { super(message, args); + this.resourceType = resourceType; + this.resourceName = resourceName; + } + + public ErrorResponseResourceType resourceType() { + return resourceType; + } + + public String resourceName() { + return resourceName; } } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java index 43c13b1a1c97..79c9aa4e6773 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/NotAuthorizedException.java @@ -20,6 +20,7 @@ /** Exception thrown on HTTP 401 Unauthorized. */ public class NotAuthorizedException extends RESTException { + public NotAuthorizedException(String message, Object... args) { super(String.format(message, args)); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/RESTException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/RESTException.java index 532936f43032..f7648c5d1e36 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/RESTException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/RESTException.java @@ -20,6 +20,7 @@ /** Base class for REST client exceptions. */ public class RESTException extends RuntimeException { + public RESTException(String message, Object... args) { super(String.format(message, args)); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java index 45c48ec0de09..1df196d90fd4 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceFailureException.java @@ -20,6 +20,7 @@ /** Exception thrown on HTTP 500 - Bad Request. */ public class ServiceFailureException extends RESTException { + public ServiceFailureException(String message, Object... args) { super(String.format(message, args)); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java index fb6a05e89f9f..c466b4c901d1 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/ServiceUnavailableException.java @@ -20,6 +20,7 @@ /** Exception thrown on HTTP 503 - service is unavailable. */ public class ServiceUnavailableException extends RESTException { + public ServiceUnavailableException(String message, Object... args) { super(String.format(message, args)); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/UnsupportedOperationException.java b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/UnsupportedOperationException.java new file mode 100644 index 000000000000..2feae109d30e --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/exceptions/UnsupportedOperationException.java @@ -0,0 +1,27 @@ +/* + * 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.paimon.rest.exceptions; + +/** Exception thrown on HTTP 501 - UnsupportedOperationException. */ +public class UnsupportedOperationException extends RESTException { + + public UnsupportedOperationException(String message, Object... args) { + super(String.format(message, args)); + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java index eb95ff448a2e..8e88a37b118d 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponse.java @@ -36,9 +36,17 @@ public class ErrorResponse implements RESTResponse { private static final String FIELD_MESSAGE = "message"; + private static final String FIELD_RESOURCE_TYPE = "resourceType"; + private static final String FIELD_RESOURCE_NAME = "resourceName"; private static final String FIELD_CODE = "code"; private static final String FIELD_STACK = "stack"; + @JsonProperty(FIELD_RESOURCE_TYPE) + private final ErrorResponseResourceType resourceType; + + @JsonProperty(FIELD_RESOURCE_NAME) + private final String resourceName; + @JsonProperty(FIELD_MESSAGE) private final String message; @@ -48,7 +56,13 @@ public class ErrorResponse implements RESTResponse { @JsonProperty(FIELD_STACK) private final List stack; - public ErrorResponse(String message, Integer code) { + public ErrorResponse( + ErrorResponseResourceType resourceType, + String resourceName, + String message, + Integer code) { + this.resourceType = resourceType; + this.resourceName = resourceName; this.code = code; this.message = message; this.stack = new ArrayList(); @@ -56,25 +70,33 @@ public ErrorResponse(String message, Integer code) { @JsonCreator public ErrorResponse( + @JsonProperty(FIELD_RESOURCE_TYPE) ErrorResponseResourceType resourceType, + @JsonProperty(FIELD_RESOURCE_NAME) String resourceName, @JsonProperty(FIELD_MESSAGE) String message, @JsonProperty(FIELD_CODE) int code, @JsonProperty(FIELD_STACK) List stack) { + this.resourceType = resourceType; + this.resourceName = resourceName; this.message = message; this.code = code; this.stack = stack; } - public ErrorResponse(String message, int code, Throwable throwable) { - this.message = message; - this.code = code; - this.stack = getStackFromThrowable(throwable); - } - @JsonGetter(FIELD_MESSAGE) public String getMessage() { return message; } + @JsonGetter(FIELD_RESOURCE_TYPE) + public ErrorResponseResourceType getResourceType() { + return resourceType; + } + + @JsonGetter(FIELD_RESOURCE_NAME) + public String getResourceName() { + return resourceName; + } + @JsonGetter(FIELD_CODE) public Integer getCode() { return code; diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponseResourceType.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponseResourceType.java new file mode 100644 index 000000000000..590f38e720d4 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/ErrorResponseResourceType.java @@ -0,0 +1,26 @@ +/* + * 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.paimon.rest.responses; + +/** The type of resource that caused the error. */ +public enum ErrorResponseResourceType { + DATABASE, + TABLE, + COLUMN, +} diff --git a/paimon-core/src/test/java/org/apache/paimon/catalog/CatalogTestBase.java b/paimon-core/src/test/java/org/apache/paimon/catalog/CatalogTestBase.java index 31c4c8e682b8..f7aa4ab5a601 100644 --- a/paimon-core/src/test/java/org/apache/paimon/catalog/CatalogTestBase.java +++ b/paimon-core/src/test/java/org/apache/paimon/catalog/CatalogTestBase.java @@ -25,6 +25,7 @@ import org.apache.paimon.options.Options; import org.apache.paimon.schema.Schema; import org.apache.paimon.schema.SchemaChange; +import org.apache.paimon.table.FileStoreTable; import org.apache.paimon.table.FormatTable; import org.apache.paimon.table.Table; import org.apache.paimon.types.DataField; @@ -37,7 +38,6 @@ import org.apache.paimon.shade.guava30.com.google.common.collect.Maps; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -49,12 +49,16 @@ import java.util.Map; import java.util.stream.Collectors; +import static org.apache.paimon.catalog.Catalog.SYSTEM_DATABASE_NAME; +import static org.apache.paimon.table.system.AllTableOptionsTable.ALL_TABLE_OPTIONS; +import static org.apache.paimon.table.system.CatalogOptionsTable.CATALOG_OPTIONS; import static org.apache.paimon.testutils.assertj.PaimonAssertions.anyCauseMatches; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; /** Base test class of paimon catalog in {@link Catalog}. */ public abstract class CatalogTestBase { @@ -123,7 +127,7 @@ public void testDuplicatedDatabaseAfterCreatingTable() throws Exception { List databases = catalog.listDatabases(); List distinctDatabases = databases.stream().distinct().collect(Collectors.toList()); - Assertions.assertEquals(distinctDatabases.size(), databases.size()); + assertEquals(distinctDatabases.size(), databases.size()); } @Test @@ -146,6 +150,56 @@ public void testCreateDatabase() throws Exception { .doesNotThrowAnyException(); } + @Test + public void testAlterDatabase() throws Exception { + if (!supportsAlterDatabase()) { + return; + } + // Alter database + String databaseName = "db_to_alter"; + catalog.createDatabase(databaseName, false); + String key = "key1"; + String key2 = "key2"; + // Add property + catalog.alterDatabase( + databaseName, + Lists.newArrayList( + PropertyChange.setProperty(key, "value"), + PropertyChange.setProperty(key2, "value")), + false); + Database db = catalog.getDatabase(databaseName); + assertEquals("value", db.options().get(key)); + assertEquals("value", db.options().get(key2)); + // Update property + catalog.alterDatabase( + databaseName, + Lists.newArrayList( + PropertyChange.setProperty(key, "value1"), + PropertyChange.setProperty(key2, "value1")), + false); + db = catalog.getDatabase(databaseName); + assertEquals("value1", db.options().get(key)); + assertEquals("value1", db.options().get(key2)); + // remove property + catalog.alterDatabase( + databaseName, + Lists.newArrayList( + PropertyChange.removeProperty(key), PropertyChange.removeProperty(key2)), + false); + db = catalog.getDatabase(databaseName); + assertFalse(db.options().containsKey(key)); + assertFalse(db.options().containsKey(key2)); + // Remove non-existent property + catalog.alterDatabase( + databaseName, + Lists.newArrayList( + PropertyChange.removeProperty(key), PropertyChange.removeProperty(key2)), + false); + db = catalog.getDatabase(databaseName); + assertFalse(db.options().containsKey(key)); + assertFalse(db.options().containsKey(key2)); + } + @Test public void testDropDatabase() throws Exception { // Drop database deletes the database when it exists and there are no tables @@ -193,6 +247,10 @@ public void testListTables() throws Exception { tables = catalog.listTables("test_db"); assertThat(tables).containsExactlyInAnyOrder("table1", "table2", "table3"); + + // List tables throws DatabaseNotExistException when the database does not exist + assertThatExceptionOfType(Catalog.DatabaseNotExistException.class) + .isThrownBy(() -> catalog.listTables("non_existing_db")); } @Test @@ -225,8 +283,17 @@ public void testCreateTable() throws Exception { .withMessage("The value of auto-create property should be false."); schema.options().remove(CoreOptions.AUTO_CREATE.key()); + // Create table and check the schema + schema.options().put("k1", "v1"); catalog.createTable(identifier, schema, false); - catalog.getTable(identifier); + FileStoreTable dataTable = (FileStoreTable) catalog.getTable(identifier); + assertThat(dataTable.schema().toSchema().fields()).isEqualTo(schema.fields()); + assertThat(dataTable.schema().toSchema().partitionKeys()).isEqualTo(schema.partitionKeys()); + assertThat(dataTable.schema().toSchema().comment()).isEqualTo(schema.comment()); + assertThat(dataTable.schema().toSchema().primaryKeys()).isEqualTo(schema.primaryKeys()); + for (Map.Entry option : schema.options().entrySet()) { + assertThat(dataTable.options().get(option.getKey())).isEqualTo(option.getValue()); + } // Create table throws Exception when table is system table assertThatExceptionOfType(IllegalArgumentException.class) @@ -358,6 +425,20 @@ public void testGetTable() throws Exception { .isThrownBy( () -> catalog.getTable(Identifier.create("non_existing_db", "test_table"))) .withMessage("Table non_existing_db.test_table does not exist."); + + // Get all table options from system database + if (!supportGetFromSystemDatabase()) { + return; + } + Table allTableOptionsTable = + catalog.getTable(Identifier.create(SYSTEM_DATABASE_NAME, ALL_TABLE_OPTIONS)); + assertThat(allTableOptionsTable).isNotNull(); + Table catalogOptionsTable = + catalog.getTable(Identifier.create(SYSTEM_DATABASE_NAME, CATALOG_OPTIONS)); + assertThat(catalogOptionsTable).isNotNull(); + assertThatExceptionOfType(Catalog.TableNotExistException.class) + .isThrownBy( + () -> catalog.getTable(Identifier.create(SYSTEM_DATABASE_NAME, "1111"))); } @Test @@ -541,10 +622,7 @@ public void testAlterTableRenameColumn() throws Exception { Lists.newArrayList( SchemaChange.renameColumn("col2", "new_col1")), false)) - .satisfies( - anyCauseMatches( - Catalog.ColumnAlreadyExistException.class, - "Column new_col1 already exists in the test_db.test_table table.")); + .isInstanceOf(Catalog.ColumnAlreadyExistException.class); // Alter table renames a column throws ColumnNotExistException when column does not exist assertThatThrownBy( @@ -555,10 +633,7 @@ public void testAlterTableRenameColumn() throws Exception { SchemaChange.renameColumn( "non_existing_col", "new_col2")), false)) - .satisfies( - anyCauseMatches( - Catalog.ColumnNotExistException.class, - "Column non_existing_col does not exist in the test_db.test_table table.")); + .isInstanceOf(Catalog.ColumnNotExistException.class); } @Test @@ -839,10 +914,6 @@ public void testAlterTableUpdateComment() throws Exception { assertThat(table.comment().isPresent()).isFalse(); } - protected boolean supportsView() { - return false; - } - @Test public void testView() throws Exception { if (!supportsView()) { @@ -904,10 +975,6 @@ public void testView() throws Exception { .isInstanceOf(Catalog.ViewNotExistException.class); } - protected boolean supportsFormatTable() { - return false; - } - @Test public void testFormatTable() throws Exception { if (!supportsFormatTable()) { @@ -962,49 +1029,19 @@ public void testTableUUID() throws Exception { .isGreaterThan(0); } - protected void alterDatabaseWhenSupportAlter() throws Exception { - // Alter database - String databaseName = "db_to_alter"; - catalog.createDatabase(databaseName, false); - String key = "key1"; - String key2 = "key2"; - // Add property - catalog.alterDatabase( - databaseName, - Lists.newArrayList( - PropertyChange.setProperty(key, "value"), - PropertyChange.setProperty(key2, "value")), - false); - Database db = catalog.getDatabase(databaseName); - assertEquals("value", db.options().get(key)); - assertEquals("value", db.options().get(key2)); - // Update property - catalog.alterDatabase( - databaseName, - Lists.newArrayList( - PropertyChange.setProperty(key, "value1"), - PropertyChange.setProperty(key2, "value1")), - false); - db = catalog.getDatabase(databaseName); - assertEquals("value1", db.options().get(key)); - assertEquals("value1", db.options().get(key2)); - // remove property - catalog.alterDatabase( - databaseName, - Lists.newArrayList( - PropertyChange.removeProperty(key), PropertyChange.removeProperty(key2)), - false); - db = catalog.getDatabase(databaseName); - assertEquals(false, db.options().containsKey(key)); - assertEquals(false, db.options().containsKey(key2)); - // Remove non-existent property - catalog.alterDatabase( - databaseName, - Lists.newArrayList( - PropertyChange.removeProperty(key), PropertyChange.removeProperty(key2)), - false); - db = catalog.getDatabase(databaseName); - assertEquals(false, db.options().containsKey(key)); - assertEquals(false, db.options().containsKey(key2)); + protected boolean supportGetFromSystemDatabase() { + return true; + } + + protected boolean supportsAlterDatabase() { + return false; + } + + protected boolean supportsFormatTable() { + return false; + } + + protected boolean supportsView() { + return false; } } diff --git a/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java index 51e2bf5c779d..0dea9209036d 100644 --- a/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/jdbc/JdbcCatalogTest.java @@ -117,8 +117,8 @@ public void testSerializeTable() throws Exception { }); } - @Test - public void testAlterDatabase() throws Exception { - this.alterDatabaseWhenSupportAlter(); + @Override + protected boolean supportsAlterDatabase() { + return true; } } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java index 340e38f6a7f8..0de7bf05d813 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/DefaultErrorHandlerTest.java @@ -70,7 +70,7 @@ public void testHandleErrorResponse() { ServiceFailureException.class, () -> defaultErrorHandler.accept(generateErrorResponse(500))); assertThrows( - UnsupportedOperationException.class, + org.apache.paimon.rest.exceptions.UnsupportedOperationException.class, () -> defaultErrorHandler.accept(generateErrorResponse(501))); assertThrows( RESTException.class, () -> defaultErrorHandler.accept(generateErrorResponse(502))); @@ -80,6 +80,6 @@ public void testHandleErrorResponse() { } private ErrorResponse generateErrorResponse(int code) { - return new ErrorResponse("message", code, new ArrayList()); + return new ErrorResponse(null, null, "message", code, new ArrayList()); } } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java index 3baff1ccaa43..54e7d3a68eee 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/HttpClientTest.java @@ -20,6 +20,9 @@ import org.apache.paimon.rest.auth.BearTokenCredentialsProvider; import org.apache.paimon.rest.auth.CredentialsProvider; +import org.apache.paimon.rest.exceptions.BadRequestException; +import org.apache.paimon.rest.responses.ErrorResponse; +import org.apache.paimon.rest.responses.ErrorResponseResourceType; import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; @@ -31,28 +34,26 @@ import java.io.IOException; import java.time.Duration; -import java.util.HashMap; import java.util.Map; import static org.junit.Assert.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; +import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; /** Test for {@link HttpClient}. */ public class HttpClientTest { private static final String MOCK_PATH = "/v1/api/mock"; private static final String TOKEN = "token"; - - private final ObjectMapper objectMapper = RESTObjectMapper.create(); + private static final ObjectMapper OBJECT_MAPPER = RESTObjectMapper.create(); private MockWebServer mockWebServer; private HttpClient httpClient; private ErrorHandler errorHandler; private MockRESTData mockResponseData; private String mockResponseDataStr; + private ErrorResponse errorResponse; + private String errorResponseStr; private Map headers; @Before @@ -60,11 +61,13 @@ public void setUp() throws IOException { mockWebServer = new MockWebServer(); mockWebServer.start(); String baseUrl = mockWebServer.url("").toString(); - errorHandler = mock(ErrorHandler.class); + errorHandler = DefaultErrorHandler.getInstance(); HttpClientOptions httpClientOptions = new HttpClientOptions(baseUrl, Duration.ofSeconds(3), Duration.ofSeconds(3), 1); mockResponseData = new MockRESTData(MOCK_PATH); - mockResponseDataStr = objectMapper.writeValueAsString(mockResponseData); + mockResponseDataStr = OBJECT_MAPPER.writeValueAsString(mockResponseData); + errorResponse = new ErrorResponse(ErrorResponseResourceType.DATABASE, "test", "test", 400); + errorResponseStr = OBJECT_MAPPER.writeValueAsString(errorResponse); httpClient = new HttpClient(httpClientOptions); httpClient.setErrorHandler(errorHandler); CredentialsProvider credentialsProvider = new BearTokenCredentialsProvider(TOKEN); @@ -80,15 +83,15 @@ public void tearDown() throws IOException { public void testGetSuccess() { mockHttpCallWithCode(mockResponseDataStr, 200); MockRESTData response = httpClient.get(MOCK_PATH, MockRESTData.class, headers); - verify(errorHandler, times(0)).accept(any()); assertEquals(mockResponseData.data(), response.data()); } @Test public void testGetFail() { - mockHttpCallWithCode(mockResponseDataStr, 400); - httpClient.get(MOCK_PATH, MockRESTData.class, headers); - verify(errorHandler, times(1)).accept(any()); + mockHttpCallWithCode(errorResponseStr, 400); + assertThrows( + BadRequestException.class, + () -> httpClient.get(MOCK_PATH, MockRESTData.class, headers)); } @Test @@ -96,35 +99,27 @@ public void testPostSuccess() { mockHttpCallWithCode(mockResponseDataStr, 200); MockRESTData response = httpClient.post(MOCK_PATH, mockResponseData, MockRESTData.class, headers); - verify(errorHandler, times(0)).accept(any()); assertEquals(mockResponseData.data(), response.data()); } @Test public void testPostFail() { - mockHttpCallWithCode(mockResponseDataStr, 400); - httpClient.post(MOCK_PATH, mockResponseData, MockRESTData.class, headers); - verify(errorHandler, times(1)).accept(any()); + mockHttpCallWithCode(errorResponseStr, 400); + assertThrows( + BadRequestException.class, + () -> httpClient.post(MOCK_PATH, mockResponseData, ErrorResponse.class, headers)); } @Test public void testDeleteSuccess() { mockHttpCallWithCode(mockResponseDataStr, 200); - MockRESTData response = httpClient.delete(MOCK_PATH, headers); - verify(errorHandler, times(0)).accept(any()); + assertDoesNotThrow(() -> httpClient.delete(MOCK_PATH, headers)); } @Test public void testDeleteFail() { - mockHttpCallWithCode(mockResponseDataStr, 400); - httpClient.delete(MOCK_PATH, headers); - verify(errorHandler, times(1)).accept(any()); - } - - private Map headers(String token) { - Map header = new HashMap<>(); - header.put("Authorization", "Bearer " + token); - return header; + mockHttpCallWithCode(errorResponseStr, 400); + assertThrows(BadRequestException.class, () -> httpClient.delete(MOCK_PATH, headers)); } private void mockHttpCallWithCode(String body, Integer code) { diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java index 4b228d93c6f7..58a73bbfcbaa 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/MockRESTMessage.java @@ -30,7 +30,6 @@ import org.apache.paimon.rest.requests.RenameTableRequest; import org.apache.paimon.rest.responses.AlterDatabaseResponse; import org.apache.paimon.rest.responses.CreateDatabaseResponse; -import org.apache.paimon.rest.responses.ErrorResponse; import org.apache.paimon.rest.responses.GetDatabaseResponse; import org.apache.paimon.rest.responses.GetTableResponse; import org.apache.paimon.rest.responses.ListDatabasesResponse; @@ -48,6 +47,8 @@ import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableList; import org.apache.paimon.shade.guava30.com.google.common.collect.Lists; +import okhttp3.mockwebserver.MockResponse; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -89,10 +90,6 @@ public static ListDatabasesResponse listDatabasesResponse(String name) { return new ListDatabasesResponse(databaseNameList); } - public static ErrorResponse noSuchResourceExceptionErrorResponse() { - return new ErrorResponse("message", 404, new ArrayList<>()); - } - public static AlterDatabaseRequest alterDatabaseRequest() { Map add = new HashMap<>(); add.put("add", "value"); @@ -243,6 +240,13 @@ public static GetTableResponse getTableResponse() { return new GetTableResponse("/tmp/1", 1, schema(options)); } + public static MockResponse mockResponse(String body, int httpCode) { + return new MockResponse() + .setResponseCode(httpCode) + .setBody(body) + .addHeader("Content-Type", "application/json"); + } + private static Schema schema(Map options) { List fields = Arrays.asList( diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java new file mode 100644 index 000000000000..4fe20291135c --- /dev/null +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java @@ -0,0 +1,329 @@ +/* + * 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.paimon.rest; + +import org.apache.paimon.catalog.AbstractCatalog; +import org.apache.paimon.catalog.Catalog; +import org.apache.paimon.catalog.CatalogContext; +import org.apache.paimon.catalog.CatalogFactory; +import org.apache.paimon.catalog.Database; +import org.apache.paimon.catalog.Identifier; +import org.apache.paimon.options.CatalogOptions; +import org.apache.paimon.options.Options; +import org.apache.paimon.partition.Partition; +import org.apache.paimon.rest.requests.AlterTableRequest; +import org.apache.paimon.rest.requests.CreateDatabaseRequest; +import org.apache.paimon.rest.requests.CreateTableRequest; +import org.apache.paimon.rest.requests.RenameTableRequest; +import org.apache.paimon.rest.responses.CreateDatabaseResponse; +import org.apache.paimon.rest.responses.ErrorResponse; +import org.apache.paimon.rest.responses.ErrorResponseResourceType; +import org.apache.paimon.rest.responses.GetDatabaseResponse; +import org.apache.paimon.rest.responses.GetTableResponse; +import org.apache.paimon.rest.responses.ListDatabasesResponse; +import org.apache.paimon.rest.responses.ListPartitionsResponse; +import org.apache.paimon.rest.responses.ListTablesResponse; +import org.apache.paimon.table.FileStoreTable; + +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonProcessingException; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; + +import okhttp3.mockwebserver.Dispatcher; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; + +import java.io.IOException; +import java.util.List; + +/** Mock REST server for testing. */ +public class RESTCatalogServer { + + private static final ObjectMapper OBJECT_MAPPER = RESTObjectMapper.create(); + private static final String PREFIX = "paimon"; + private static final String DATABASE_URI = String.format("/v1/%s/databases", PREFIX); + + private final Catalog catalog; + private final Dispatcher dispatcher; + private final MockWebServer server; + private final String authToken; + + public RESTCatalogServer(String warehouse, String initToken) { + authToken = initToken; + Options conf = new Options(); + conf.setString("warehouse", warehouse); + this.catalog = + CatalogFactory.createCatalog( + CatalogContext.create(conf), this.getClass().getClassLoader()); + this.dispatcher = initDispatcher(catalog, authToken); + MockWebServer mockWebServer = new MockWebServer(); + mockWebServer.setDispatcher(dispatcher); + server = mockWebServer; + } + + public void start() throws IOException { + server.start(); + } + + public String getUrl() { + return server.url("").toString(); + } + + public void shutdown() throws IOException { + server.shutdown(); + } + + public static Dispatcher initDispatcher(Catalog catalog, String authToken) { + return new Dispatcher() { + @Override + public MockResponse dispatch(RecordedRequest request) { + String token = request.getHeaders().get("Authorization"); + RESTResponse response; + try { + if (!("Bearer " + authToken).equals(token)) { + return new MockResponse().setResponseCode(401); + } + if ("/v1/config".equals(request.getPath())) { + return new MockResponse() + .setResponseCode(200) + .setBody(getConfigBody(catalog.warehouse())); + } else if (DATABASE_URI.equals(request.getPath())) { + return databasesApiHandler(catalog, request); + } else if (request.getPath().startsWith(DATABASE_URI)) { + String[] resources = + request.getPath() + .substring((DATABASE_URI + "/").length()) + .split("/"); + String databaseName = resources[0]; + boolean isTables = resources.length == 2 && "tables".equals(resources[1]); + boolean isTable = resources.length == 3 && "tables".equals(resources[1]); + boolean isTableRename = + resources.length == 4 && "rename".equals(resources[3]); + boolean isPartitions = + resources.length == 4 + && "tables".equals(resources[1]) + && "partitions".equals(resources[3]); + if (isPartitions) { + String tableName = resources[2]; + List partitions = + catalog.listPartitions( + Identifier.create(databaseName, tableName)); + response = new ListPartitionsResponse(partitions); + return mockResponse(response, 200); + } else if (isTableRename) { + return renameTableApiHandler( + catalog, request, databaseName, resources[2]); + } else if (isTable) { + String tableName = resources[2]; + return tableApiHandler(catalog, request, databaseName, tableName); + } else if (isTables) { + return tablesApiHandler(catalog, request, databaseName); + } else { + return databaseApiHandler(catalog, request, databaseName); + } + } + return new MockResponse().setResponseCode(404); + } catch (Catalog.DatabaseNotExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.DATABASE, + e.database(), + e.getMessage(), + 404); + return mockResponse(response, 404); + } catch (Catalog.TableNotExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.TABLE, + e.identifier().getTableName(), + e.getMessage(), + 404); + return mockResponse(response, 404); + } catch (Catalog.ColumnNotExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.COLUMN, + e.column(), + e.getMessage(), + 404); + return mockResponse(response, 404); + } catch (Catalog.DatabaseAlreadyExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.DATABASE, + e.database(), + e.getMessage(), + 409); + return mockResponse(response, 409); + } catch (Catalog.TableAlreadyExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.TABLE, + e.identifier().getTableName(), + e.getMessage(), + 409); + return mockResponse(response, 409); + } catch (Catalog.ColumnAlreadyExistException e) { + response = + new ErrorResponse( + ErrorResponseResourceType.COLUMN, + e.column(), + e.getMessage(), + 409); + return mockResponse(response, 409); + } catch (IllegalArgumentException e) { + response = new ErrorResponse(null, null, e.getMessage(), 400); + return mockResponse(response, 400); + } catch (Exception e) { + if (e.getCause() instanceof IllegalArgumentException) { + response = + new ErrorResponse( + null, null, e.getCause().getCause().getMessage(), 400); + return mockResponse(response, 400); + } else if (e instanceof UnsupportedOperationException) { + response = new ErrorResponse(null, null, e.getMessage(), 501); + return mockResponse(response, 501); + } else if (e instanceof IllegalStateException) { + response = new ErrorResponse(null, null, e.getMessage(), 500); + return mockResponse(response, 500); + } + return new MockResponse().setResponseCode(500); + } + } + }; + } + + private static MockResponse renameTableApiHandler( + Catalog catalog, RecordedRequest request, String databaseName, String tableName) + throws Exception { + RenameTableRequest requestBody = + OBJECT_MAPPER.readValue(request.getBody().readUtf8(), RenameTableRequest.class); + catalog.renameTable( + Identifier.create(databaseName, tableName), requestBody.getNewIdentifier(), false); + FileStoreTable table = (FileStoreTable) catalog.getTable(requestBody.getNewIdentifier()); + RESTResponse response = + new GetTableResponse( + AbstractCatalog.newTableLocation( + catalog.warehouse(), requestBody.getNewIdentifier()) + .toString(), + table.schema().id(), + table.schema().toSchema()); + return mockResponse(response, 200); + } + + private static MockResponse databasesApiHandler(Catalog catalog, RecordedRequest request) + throws Exception { + RESTResponse response; + if (request.getMethod().equals("GET")) { + List databaseNameList = catalog.listDatabases(); + response = new ListDatabasesResponse(databaseNameList); + return mockResponse(response, 200); + } else if (request.getMethod().equals("POST")) { + CreateDatabaseRequest requestBody = + OBJECT_MAPPER.readValue( + request.getBody().readUtf8(), CreateDatabaseRequest.class); + String databaseName = requestBody.getName(); + catalog.createDatabase(databaseName, false); + response = new CreateDatabaseResponse(databaseName, requestBody.getOptions()); + return mockResponse(response, 200); + } + return new MockResponse().setResponseCode(404); + } + + private static MockResponse databaseApiHandler( + Catalog catalog, RecordedRequest request, String databaseName) throws Exception { + RESTResponse response; + if (request.getMethod().equals("GET")) { + Database database = catalog.getDatabase(databaseName); + response = new GetDatabaseResponse(database.name(), database.options()); + return mockResponse(response, 200); + } else if (request.getMethod().equals("DELETE")) { + catalog.dropDatabase(databaseName, false, true); + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + + private static MockResponse tablesApiHandler( + Catalog catalog, RecordedRequest request, String databaseName) throws Exception { + RESTResponse response; + if (request.getMethod().equals("POST")) { + CreateTableRequest requestBody = + OBJECT_MAPPER.readValue(request.getBody().readUtf8(), CreateTableRequest.class); + catalog.createTable(requestBody.getIdentifier(), requestBody.getSchema(), false); + response = new GetTableResponse("", 1L, requestBody.getSchema()); + return mockResponse(response, 200); + } else if (request.getMethod().equals("GET")) { + catalog.listTables(databaseName); + response = new ListTablesResponse(catalog.listTables(databaseName)); + return mockResponse(response, 200); + } + return new MockResponse().setResponseCode(404); + } + + private static MockResponse tableApiHandler( + Catalog catalog, RecordedRequest request, String databaseName, String tableName) + throws Exception { + RESTResponse response; + if (request.getMethod().equals("GET")) { + Identifier identifier = Identifier.create(databaseName, tableName); + FileStoreTable table = (FileStoreTable) catalog.getTable(identifier); + response = + new GetTableResponse( + AbstractCatalog.newTableLocation(catalog.warehouse(), identifier) + .toString(), + table.schema().id(), + table.schema().toSchema()); + return mockResponse(response, 200); + } else if (request.getMethod().equals("POST")) { + Identifier identifier = Identifier.create(databaseName, tableName); + AlterTableRequest requestBody = + OBJECT_MAPPER.readValue(request.getBody().readUtf8(), AlterTableRequest.class); + catalog.alterTable(identifier, requestBody.getChanges(), false); + FileStoreTable table = (FileStoreTable) catalog.getTable(identifier); + response = new GetTableResponse("", table.schema().id(), table.schema().toSchema()); + return mockResponse(response, 200); + } else if (request.getMethod().equals("DELETE")) { + Identifier identifier = Identifier.create(databaseName, tableName); + catalog.dropTable(identifier, false); + return new MockResponse().setResponseCode(200); + } + return new MockResponse().setResponseCode(404); + } + + private static MockResponse mockResponse(RESTResponse response, int httpCode) { + try { + return new MockResponse() + .setResponseCode(httpCode) + .setBody(OBJECT_MAPPER.writeValueAsString(response)) + .addHeader("Content-Type", "application/json"); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + private static String getConfigBody(String warehouseStr) { + return String.format( + "{\"defaults\": {\"%s\": \"%s\", \"%s\": \"%s\"}}", + RESTCatalogInternalOptions.PREFIX.key(), + PREFIX, + CatalogOptions.WAREHOUSE.key(), + warehouseStr); + } +} diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java index 344807b4c96e..b34ca1e5acd1 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java @@ -18,352 +18,107 @@ package org.apache.paimon.rest; -import org.apache.paimon.catalog.Catalog; import org.apache.paimon.catalog.CatalogContext; -import org.apache.paimon.catalog.Database; +import org.apache.paimon.catalog.CatalogTestBase; import org.apache.paimon.catalog.Identifier; import org.apache.paimon.options.CatalogOptions; import org.apache.paimon.options.Options; import org.apache.paimon.partition.Partition; -import org.apache.paimon.rest.requests.CreateTableRequest; -import org.apache.paimon.rest.responses.AlterDatabaseResponse; -import org.apache.paimon.rest.responses.CreateDatabaseResponse; -import org.apache.paimon.rest.responses.ErrorResponse; -import org.apache.paimon.rest.responses.GetDatabaseResponse; -import org.apache.paimon.rest.responses.GetTableResponse; -import org.apache.paimon.rest.responses.ListDatabasesResponse; -import org.apache.paimon.rest.responses.ListPartitionsResponse; -import org.apache.paimon.rest.responses.ListTablesResponse; -import org.apache.paimon.schema.SchemaChange; -import org.apache.paimon.table.Table; +import org.apache.paimon.rest.exceptions.NotAuthorizedException; +import org.apache.paimon.schema.Schema; +import org.apache.paimon.types.DataField; +import org.apache.paimon.types.DataTypes; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.core.JsonProcessingException; -import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.paimon.shade.guava30.com.google.common.collect.Lists; +import org.apache.paimon.shade.guava30.com.google.common.collect.Maps; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThrows; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertEquals; /** Test for REST Catalog. */ -public class RESTCatalogTest { +class RESTCatalogTest extends CatalogTestBase { - private final ObjectMapper mapper = RESTObjectMapper.create(); - private MockWebServer mockWebServer; - private RESTCatalog restCatalog; - private String warehouseStr; - private String serverUrl; - @Rule public TemporaryFolder folder = new TemporaryFolder(); + private RESTCatalogServer restCatalogServer; - @Before - public void setUp() throws IOException { - mockWebServer = new MockWebServer(); - mockWebServer.start(); - serverUrl = mockWebServer.url("").toString(); - Options options = mockInitOptions(); - warehouseStr = folder.getRoot().getPath(); - mockConfig(warehouseStr); - restCatalog = new RESTCatalog(CatalogContext.create(options)); - } - - @After - public void tearDown() throws IOException { - mockWebServer.shutdown(); - } - - @Test - public void testInitFailWhenDefineWarehouse() { + @BeforeEach + @Override + public void setUp() throws Exception { + super.setUp(); + String initToken = "init_token"; + restCatalogServer = new RESTCatalogServer(warehouse, initToken); + restCatalogServer.start(); Options options = new Options(); - options.set(CatalogOptions.WAREHOUSE, warehouseStr); - assertThrows( - IllegalArgumentException.class, - () -> new RESTCatalog(CatalogContext.create(options))); - } - - @Test - public void testListDatabases() throws JsonProcessingException { - String name = MockRESTMessage.databaseName(); - ListDatabasesResponse response = MockRESTMessage.listDatabasesResponse(name); - mockResponse(mapper.writeValueAsString(response), 200); - List result = restCatalog.listDatabases(); - assertEquals(response.getDatabases().size(), result.size()); - assertEquals(name, result.get(0)); - } - - @Test - public void testCreateDatabase() throws Exception { - String name = MockRESTMessage.databaseName(); - CreateDatabaseResponse response = MockRESTMessage.createDatabaseResponse(name); - mockResponse(mapper.writeValueAsString(response), 200); - assertDoesNotThrow(() -> restCatalog.createDatabase(name, false, response.getOptions())); - } - - @Test - public void testGetDatabase() throws Exception { - String name = MockRESTMessage.databaseName(); - GetDatabaseResponse response = MockRESTMessage.getDatabaseResponse(name); - mockResponse(mapper.writeValueAsString(response), 200); - Database result = restCatalog.getDatabase(name); - assertEquals(name, result.name()); - assertEquals(response.getOptions().size(), result.options().size()); - assertEquals(response.comment().get(), result.comment().get()); + options.set(RESTCatalogOptions.URI, restCatalogServer.getUrl()); + options.set(RESTCatalogOptions.TOKEN, initToken); + options.set(RESTCatalogOptions.THREAD_POOL_SIZE, 1); + this.catalog = new RESTCatalog(CatalogContext.create(options)); } - @Test - public void testDropDatabase() throws Exception { - String name = MockRESTMessage.databaseName(); - mockResponse("", 200); - assertDoesNotThrow(() -> restCatalog.dropDatabase(name, false, true)); + @AfterEach + public void tearDown() throws Exception { + restCatalogServer.shutdown(); } - @Test - public void testDropDatabaseWhenNoExistAndIgnoreIfNotExistsIsFalse() throws Exception { - String name = MockRESTMessage.databaseName(); - ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); - mockResponse(mapper.writeValueAsString(response), 404); - assertThrows( - Catalog.DatabaseNotExistException.class, - () -> restCatalog.dropDatabase(name, false, true)); + @Override + protected boolean supportGetFromSystemDatabase() { + return false; } @Test - public void testDropDatabaseWhenNoExistAndIgnoreIfNotExistsIsTrue() throws Exception { - String name = MockRESTMessage.databaseName(); - ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); - mockResponse(mapper.writeValueAsString(response), 404); - assertDoesNotThrow(() -> restCatalog.dropDatabase(name, true, true)); + void testInitFailWhenDefineWarehouse() { + Options options = new Options(); + options.set(CatalogOptions.WAREHOUSE, warehouse); + assertThatThrownBy(() -> new RESTCatalog(CatalogContext.create(options))) + .isInstanceOf(IllegalArgumentException.class); } @Test - public void testDropDatabaseWhenCascadeIsFalseAndNoTables() throws Exception { - String name = MockRESTMessage.databaseName(); - boolean cascade = false; - ListTablesResponse response = MockRESTMessage.listTablesEmptyResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - mockResponse("", 200); - assertDoesNotThrow(() -> restCatalog.dropDatabase(name, false, cascade)); + void testAuthFail() { + Options options = new Options(); + options.set(RESTCatalogOptions.URI, restCatalogServer.getUrl()); + options.set(RESTCatalogOptions.TOKEN, "aaaaa"); + options.set(RESTCatalogOptions.THREAD_POOL_SIZE, 1); + options.set(CatalogOptions.METASTORE, RESTCatalogFactory.IDENTIFIER); + assertThatThrownBy(() -> new RESTCatalog(CatalogContext.create(options))) + .isInstanceOf(NotAuthorizedException.class); } @Test - public void testDropDatabaseWhenCascadeIsFalseAndTablesExist() throws Exception { - String name = MockRESTMessage.databaseName(); - boolean cascade = false; - ListTablesResponse response = MockRESTMessage.listTablesResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - assertThrows( - Catalog.DatabaseNotEmptyException.class, - () -> restCatalog.dropDatabase(name, false, cascade)); + void testListPartitionsWhenMetastorePartitionedIsTrue() throws Exception { + Identifier identifier = Identifier.create("test_db", "test_table"); + createTable(identifier, Maps.newHashMap(), Lists.newArrayList("col1")); + List result = catalog.listPartitions(identifier); + assertEquals(0, result.size()); } @Test - public void testAlterDatabase() throws Exception { - String name = MockRESTMessage.databaseName(); - AlterDatabaseResponse response = MockRESTMessage.alterDatabaseResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - assertDoesNotThrow(() -> restCatalog.alterDatabase(name, new ArrayList<>(), true)); + void testListPartitionsFromFile() throws Exception { + Identifier identifier = Identifier.create("test_db", "test_table"); + createTable(identifier, Maps.newHashMap(), Lists.newArrayList("col1")); + List result = catalog.listPartitions(identifier); + assertEquals(0, result.size()); } - @Test - public void testAlterDatabaseWhenDatabaseNotExistAndIgnoreIfNotExistsIsFalse() + private void createTable( + Identifier identifier, Map options, List partitionKeys) throws Exception { - String name = MockRESTMessage.databaseName(); - ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); - mockResponse(mapper.writeValueAsString(response), 404); - assertThrows( - Catalog.DatabaseNotExistException.class, - () -> restCatalog.alterDatabase(name, new ArrayList<>(), false)); - } - - @Test - public void testAlterDatabaseWhenDatabaseNotExistAndIgnoreIfNotExistsIsTrue() throws Exception { - String name = MockRESTMessage.databaseName(); - ErrorResponse response = MockRESTMessage.noSuchResourceExceptionErrorResponse(); - mockResponse(mapper.writeValueAsString(response), 404); - assertDoesNotThrow(() -> restCatalog.alterDatabase(name, new ArrayList<>(), true)); - } - - @Test - public void testListTables() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - ListTablesResponse response = MockRESTMessage.listTablesResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - List result = restCatalog.listTables(databaseName); - assertEquals(response.getTables().size(), result.size()); - } - - @Test - public void testGetTable() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - GetTableResponse response = MockRESTMessage.getTableResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - Table result = restCatalog.getTable(Identifier.create(databaseName, "table")); - assertEquals(response.getSchema().options().size() + 1, result.options().size()); - } - - @Test - public void testCreateTable() throws Exception { - CreateTableRequest request = MockRESTMessage.createTableRequest("table"); - GetTableResponse response = MockRESTMessage.getTableResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - assertDoesNotThrow( - () -> restCatalog.createTable(request.getIdentifier(), request.getSchema(), false)); - } - - @Test - public void testCreateTableWhenTableAlreadyExistAndIgnoreIfExistsIsFalse() throws Exception { - CreateTableRequest request = MockRESTMessage.createTableRequest("table"); - mockResponse("", 409); - assertThrows( - Catalog.TableAlreadyExistException.class, - () -> restCatalog.createTable(request.getIdentifier(), request.getSchema(), false)); - } - - @Test - public void testRenameTable() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - String fromTableName = "fromTable"; - String toTableName = "toTable"; - GetTableResponse response = MockRESTMessage.getTableResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - assertDoesNotThrow( - () -> - restCatalog.renameTable( - Identifier.create(databaseName, fromTableName), - Identifier.create(databaseName, toTableName), - true)); - } - - @Test - public void testRenameTableWhenTableNotExistAndIgnoreIfNotExistsIsFalse() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - String fromTableName = "fromTable"; - String toTableName = "toTable"; - mockResponse("", 404); - assertThrows( - Catalog.TableNotExistException.class, - () -> - restCatalog.renameTable( - Identifier.create(databaseName, fromTableName), - Identifier.create(databaseName, toTableName), - false)); - } - - @Test - public void testRenameTableWhenToTableAlreadyExist() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - String fromTableName = "fromTable"; - String toTableName = "toTable"; - mockResponse("", 409); - assertThrows( - Catalog.TableAlreadyExistException.class, - () -> - restCatalog.renameTable( - Identifier.create(databaseName, fromTableName), - Identifier.create(databaseName, toTableName), - false)); - } - - @Test - public void testAlterTable() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - List changes = MockRESTMessage.getChanges(); - GetTableResponse response = MockRESTMessage.getTableResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - assertDoesNotThrow( - () -> restCatalog.alterTable(Identifier.create(databaseName, "t1"), changes, true)); - } - - @Test - public void testAlterTableWhenTableNotExistAndIgnoreIfNotExistsIsFalse() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - List changes = MockRESTMessage.getChanges(); - mockResponse("", 404); - assertThrows( - Catalog.TableNotExistException.class, - () -> - restCatalog.alterTable( - Identifier.create(databaseName, "t1"), changes, false)); - } - - @Test - public void testDropTable() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - String tableName = "table"; - mockResponse("", 200); - assertDoesNotThrow( - () -> restCatalog.dropTable(Identifier.create(databaseName, tableName), true)); - } - - @Test - public void testDropTableWhenTableNotExistAndIgnoreIfNotExistsIsFalse() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - String tableName = "table"; - mockResponse("", 404); - assertThrows( - Catalog.TableNotExistException.class, - () -> restCatalog.dropTable(Identifier.create(databaseName, tableName), false)); - } - - @Test - public void testListPartitionsWhenMetastorePartitionedIsTrue() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - GetTableResponse getTableResponse = MockRESTMessage.getTableResponseEnablePartition(); - mockResponse(mapper.writeValueAsString(getTableResponse), 200); - ListPartitionsResponse response = MockRESTMessage.listPartitionsResponse(); - mockResponse(mapper.writeValueAsString(response), 200); - List result = - restCatalog.listPartitions(Identifier.create(databaseName, "table")); - assertEquals(response.getPartitions().size(), result.size()); - } - - @Test - public void testListPartitionsFromFile() throws Exception { - String databaseName = MockRESTMessage.databaseName(); - GetTableResponse response = MockRESTMessage.getTableResponseEnablePartition(); - mockResponse(mapper.writeValueAsString(response), 200); - mockResponse(mapper.writeValueAsString(response), 200); - List partitionEntries = - restCatalog.listPartitions(Identifier.create(databaseName, "table")); - assertEquals(partitionEntries.size(), 0); - } - - private void mockResponse(String mockResponse, int httpCode) { - MockResponse mockResponseObj = - new MockResponse() - .setResponseCode(httpCode) - .setBody(mockResponse) - .addHeader("Content-Type", "application/json"); - mockWebServer.enqueue(mockResponseObj); - } - - private void mockConfig(String warehouseStr) { - String mockResponse = - String.format( - "{\"defaults\": {\"%s\": \"%s\", \"%s\": \"%s\"}}", - RESTCatalogInternalOptions.PREFIX.key(), - "prefix", - CatalogOptions.WAREHOUSE.key(), - warehouseStr); - mockResponse(mockResponse, 200); - } - - public Options mockInitOptions() { - Options options = new Options(); - options.set(RESTCatalogOptions.URI, serverUrl); - String initToken = "init_token"; - options.set(RESTCatalogOptions.TOKEN, initToken); - options.set(RESTCatalogOptions.THREAD_POOL_SIZE, 1); - return options; + catalog.createDatabase(identifier.getDatabaseName(), false); + catalog.createTable( + identifier, + new Schema( + Lists.newArrayList(new DataField(0, "col1", DataTypes.STRING())), + partitionKeys, + Collections.emptyList(), + options, + ""), + true); } } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java index 38a6e08751f9..354efe69d5d2 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTObjectMapperTest.java @@ -53,7 +53,8 @@ /** Test for {@link RESTObjectMapper}. */ public class RESTObjectMapperTest { - private ObjectMapper mapper = RESTObjectMapper.create(); + + private static final ObjectMapper OBJECT_MAPPER = RESTObjectMapper.create(); @Test public void configResponseParseTest() throws Exception { @@ -61,8 +62,8 @@ public void configResponseParseTest() throws Exception { Map conf = new HashMap<>(); conf.put(confKey, "b"); ConfigResponse response = new ConfigResponse(conf, conf); - String responseStr = mapper.writeValueAsString(response); - ConfigResponse parseData = mapper.readValue(responseStr, ConfigResponse.class); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); + ConfigResponse parseData = OBJECT_MAPPER.readValue(responseStr, ConfigResponse.class); assertEquals(conf.get(confKey), parseData.getDefaults().get(confKey)); } @@ -70,9 +71,10 @@ public void configResponseParseTest() throws Exception { public void errorResponseParseTest() throws Exception { String message = "message"; Integer code = 400; - ErrorResponse response = new ErrorResponse(message, code, new ArrayList()); - String responseStr = mapper.writeValueAsString(response); - ErrorResponse parseData = mapper.readValue(responseStr, ErrorResponse.class); + ErrorResponse response = + new ErrorResponse(null, null, message, code, new ArrayList()); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); + ErrorResponse parseData = OBJECT_MAPPER.readValue(responseStr, ErrorResponse.class); assertEquals(message, parseData.getMessage()); assertEquals(code, parseData.getCode()); } @@ -81,8 +83,9 @@ public void errorResponseParseTest() throws Exception { public void createDatabaseRequestParseTest() throws Exception { String name = MockRESTMessage.databaseName(); CreateDatabaseRequest request = MockRESTMessage.createDatabaseRequest(name); - String requestStr = mapper.writeValueAsString(request); - CreateDatabaseRequest parseData = mapper.readValue(requestStr, CreateDatabaseRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + CreateDatabaseRequest parseData = + OBJECT_MAPPER.readValue(requestStr, CreateDatabaseRequest.class); assertEquals(request.getName(), parseData.getName()); assertEquals(request.getOptions().size(), parseData.getOptions().size()); } @@ -91,9 +94,9 @@ public void createDatabaseRequestParseTest() throws Exception { public void createDatabaseResponseParseTest() throws Exception { String name = MockRESTMessage.databaseName(); CreateDatabaseResponse response = MockRESTMessage.createDatabaseResponse(name); - String responseStr = mapper.writeValueAsString(response); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); CreateDatabaseResponse parseData = - mapper.readValue(responseStr, CreateDatabaseResponse.class); + OBJECT_MAPPER.readValue(responseStr, CreateDatabaseResponse.class); assertEquals(name, parseData.getName()); assertEquals(response.getOptions().size(), parseData.getOptions().size()); } @@ -102,8 +105,9 @@ public void createDatabaseResponseParseTest() throws Exception { public void getDatabaseResponseParseTest() throws Exception { String name = MockRESTMessage.databaseName(); GetDatabaseResponse response = MockRESTMessage.getDatabaseResponse(name); - String responseStr = mapper.writeValueAsString(response); - GetDatabaseResponse parseData = mapper.readValue(responseStr, GetDatabaseResponse.class); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); + GetDatabaseResponse parseData = + OBJECT_MAPPER.readValue(responseStr, GetDatabaseResponse.class); assertEquals(name, parseData.getName()); assertEquals(response.getOptions().size(), parseData.getOptions().size()); assertEquals(response.comment().get(), parseData.comment().get()); @@ -113,9 +117,9 @@ public void getDatabaseResponseParseTest() throws Exception { public void listDatabaseResponseParseTest() throws Exception { String name = MockRESTMessage.databaseName(); ListDatabasesResponse response = MockRESTMessage.listDatabasesResponse(name); - String responseStr = mapper.writeValueAsString(response); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); ListDatabasesResponse parseData = - mapper.readValue(responseStr, ListDatabasesResponse.class); + OBJECT_MAPPER.readValue(responseStr, ListDatabasesResponse.class); assertEquals(response.getDatabases().size(), parseData.getDatabases().size()); assertEquals(name, parseData.getDatabases().get(0)); } @@ -123,8 +127,9 @@ public void listDatabaseResponseParseTest() throws Exception { @Test public void alterDatabaseRequestParseTest() throws Exception { AlterDatabaseRequest request = MockRESTMessage.alterDatabaseRequest(); - String requestStr = mapper.writeValueAsString(request); - AlterDatabaseRequest parseData = mapper.readValue(requestStr, AlterDatabaseRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + AlterDatabaseRequest parseData = + OBJECT_MAPPER.readValue(requestStr, AlterDatabaseRequest.class); assertEquals(request.getRemovals().size(), parseData.getRemovals().size()); assertEquals(request.getUpdates().size(), parseData.getUpdates().size()); } @@ -132,9 +137,9 @@ public void alterDatabaseRequestParseTest() throws Exception { @Test public void alterDatabaseResponseParseTest() throws Exception { AlterDatabaseResponse response = MockRESTMessage.alterDatabaseResponse(); - String responseStr = mapper.writeValueAsString(response); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); AlterDatabaseResponse parseData = - mapper.readValue(responseStr, AlterDatabaseResponse.class); + OBJECT_MAPPER.readValue(responseStr, AlterDatabaseResponse.class); assertEquals(response.getRemoved().size(), parseData.getRemoved().size()); assertEquals(response.getUpdated().size(), parseData.getUpdated().size()); assertEquals(response.getMissing().size(), parseData.getMissing().size()); @@ -143,8 +148,9 @@ public void alterDatabaseResponseParseTest() throws Exception { @Test public void createTableRequestParseTest() throws Exception { CreateTableRequest request = MockRESTMessage.createTableRequest("t1"); - String requestStr = mapper.writeValueAsString(request); - CreateTableRequest parseData = mapper.readValue(requestStr, CreateTableRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + CreateTableRequest parseData = + OBJECT_MAPPER.readValue(requestStr, CreateTableRequest.class); assertEquals(request.getIdentifier(), parseData.getIdentifier()); assertEquals(request.getSchema(), parseData.getSchema()); } @@ -160,7 +166,7 @@ public void dataFieldParseTest() throws Exception { String.format( "{\"id\": %d,\"name\":\"%s\",\"type\":\"%s\", \"description\":\"%s\"}", id, name, type, descStr); - DataField parseData = mapper.readValue(dataFieldStr, DataField.class); + DataField parseData = OBJECT_MAPPER.readValue(dataFieldStr, DataField.class); assertEquals(id, parseData.id()); assertEquals(name, parseData.name()); assertEquals(type, parseData.type()); @@ -170,16 +176,17 @@ public void dataFieldParseTest() throws Exception { @Test public void renameTableRequestParseTest() throws Exception { RenameTableRequest request = MockRESTMessage.renameRequest("t2"); - String requestStr = mapper.writeValueAsString(request); - RenameTableRequest parseData = mapper.readValue(requestStr, RenameTableRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + RenameTableRequest parseData = + OBJECT_MAPPER.readValue(requestStr, RenameTableRequest.class); assertEquals(request.getNewIdentifier(), parseData.getNewIdentifier()); } @Test public void getTableResponseParseTest() throws Exception { GetTableResponse response = MockRESTMessage.getTableResponse(); - String responseStr = mapper.writeValueAsString(response); - GetTableResponse parseData = mapper.readValue(responseStr, GetTableResponse.class); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); + GetTableResponse parseData = OBJECT_MAPPER.readValue(responseStr, GetTableResponse.class); assertEquals(response.getSchemaId(), parseData.getSchemaId()); assertEquals(response.getSchema(), parseData.getSchema()); } @@ -187,25 +194,26 @@ public void getTableResponseParseTest() throws Exception { @Test public void listTablesResponseParseTest() throws Exception { ListTablesResponse response = MockRESTMessage.listTablesResponse(); - String responseStr = mapper.writeValueAsString(response); - ListTablesResponse parseData = mapper.readValue(responseStr, ListTablesResponse.class); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); + ListTablesResponse parseData = + OBJECT_MAPPER.readValue(responseStr, ListTablesResponse.class); assertEquals(response.getTables(), parseData.getTables()); } @Test public void alterTableRequestParseTest() throws Exception { AlterTableRequest request = MockRESTMessage.alterTableRequest(); - String requestStr = mapper.writeValueAsString(request); - AlterTableRequest parseData = mapper.readValue(requestStr, AlterTableRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + AlterTableRequest parseData = OBJECT_MAPPER.readValue(requestStr, AlterTableRequest.class); assertEquals(parseData.getChanges().size(), parseData.getChanges().size()); } @Test public void createPartitionRequestParseTest() throws JsonProcessingException { CreatePartitionRequest request = MockRESTMessage.createPartitionRequest("t1"); - String requestStr = mapper.writeValueAsString(request); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); CreatePartitionRequest parseData = - mapper.readValue(requestStr, CreatePartitionRequest.class); + OBJECT_MAPPER.readValue(requestStr, CreatePartitionRequest.class); assertEquals(parseData.getIdentifier(), parseData.getIdentifier()); assertEquals(parseData.getPartitionSpec().size(), parseData.getPartitionSpec().size()); } @@ -213,17 +221,18 @@ public void createPartitionRequestParseTest() throws JsonProcessingException { @Test public void dropPartitionRequestParseTest() throws JsonProcessingException { DropPartitionRequest request = MockRESTMessage.dropPartitionRequest(); - String requestStr = mapper.writeValueAsString(request); - DropPartitionRequest parseData = mapper.readValue(requestStr, DropPartitionRequest.class); + String requestStr = OBJECT_MAPPER.writeValueAsString(request); + DropPartitionRequest parseData = + OBJECT_MAPPER.readValue(requestStr, DropPartitionRequest.class); assertEquals(parseData.getPartitionSpec().size(), parseData.getPartitionSpec().size()); } @Test public void listPartitionsResponseParseTest() throws Exception { ListPartitionsResponse response = MockRESTMessage.listPartitionsResponse(); - String responseStr = mapper.writeValueAsString(response); + String responseStr = OBJECT_MAPPER.writeValueAsString(response); ListPartitionsResponse parseData = - mapper.readValue(responseStr, ListPartitionsResponse.class); + OBJECT_MAPPER.readValue(responseStr, ListPartitionsResponse.class); assertEquals( response.getPartitions().get(0).fileCount(), parseData.getPartitions().get(0).fileCount()); @@ -232,10 +241,11 @@ public void listPartitionsResponseParseTest() throws Exception { @Test public void partitionResponseParseTest() throws Exception { PartitionResponse response = MockRESTMessage.partitionResponse(); - assertDoesNotThrow(() -> mapper.writeValueAsString(response)); + assertDoesNotThrow(() -> OBJECT_MAPPER.writeValueAsString(response)); assertDoesNotThrow( () -> - mapper.readValue( - mapper.writeValueAsString(response), PartitionResponse.class)); + OBJECT_MAPPER.readValue( + OBJECT_MAPPER.writeValueAsString(response), + PartitionResponse.class)); } } diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java index 1f4a48fd5e8c..fec749208273 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/auth/AuthSessionTest.java @@ -47,6 +47,23 @@ public class AuthSessionTest { @Rule public TemporaryFolder folder = new TemporaryFolder(); + @Test + public void testBearToken() { + String token = UUID.randomUUID().toString(); + Map initialHeaders = new HashMap<>(); + initialHeaders.put("k1", "v1"); + initialHeaders.put("k2", "v2"); + CredentialsProvider credentialsProvider = new BearTokenCredentialsProvider(token); + AuthSession session = new AuthSession(initialHeaders, credentialsProvider); + Map header = session.getHeaders(); + assertEquals(header.get("Authorization"), "Bearer " + token); + assertEquals(header.get("k1"), "v1"); + for (Map.Entry entry : initialHeaders.entrySet()) { + assertEquals(entry.getValue(), header.get(entry.getKey())); + } + assertEquals(header.size(), initialHeaders.size() + 1); + } + @Test public void testRefreshBearTokenFileCredentialsProvider() throws IOException, InterruptedException { diff --git a/paimon-flink/paimon-flink-common/pom.xml b/paimon-flink/paimon-flink-common/pom.xml index e0f7ce245fa7..84d4622b02b8 100644 --- a/paimon-flink/paimon-flink-common/pom.xml +++ b/paimon-flink/paimon-flink-common/pom.xml @@ -177,6 +177,13 @@ under the License. jar test + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.version} + test + diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogITCaseBase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogITCaseBase.java index 19aa6d5d7439..01d615c3e18d 100644 --- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogITCaseBase.java +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogITCaseBase.java @@ -75,7 +75,9 @@ public void before() throws IOException { Map options = new HashMap<>(catalogOptions()); options.put("type", "paimon"); - options.put("warehouse", toWarehouse(path)); + if (supportDefineWarehouse()) { + options.put("warehouse", toWarehouse(path)); + } tEnv.executeSql( String.format( "CREATE CATALOG %s WITH (" + "%s" + inferScan + ")", @@ -97,6 +99,10 @@ protected Map catalogOptions() { return Collections.emptyMap(); } + protected boolean supportDefineWarehouse() { + return true; + } + protected boolean inferScanParallelism() { return false; } diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/RESTCatalogITCase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/RESTCatalogITCase.java new file mode 100644 index 000000000000..c310de32fdb9 --- /dev/null +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/RESTCatalogITCase.java @@ -0,0 +1,116 @@ +/* + * 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.paimon.flink; + +import org.apache.paimon.rest.RESTCatalogOptions; +import org.apache.paimon.rest.RESTCatalogServer; + +import org.apache.flink.types.Row; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +/** ITCase for REST catalog. */ +class RESTCatalogITCase extends CatalogITCaseBase { + + private static final String DATABASE_NAME = "mydb"; + private static final String TABLE_NAME = "t1"; + + private RESTCatalogServer restCatalogServer; + private String serverUrl; + private String warehouse; + @TempDir java.nio.file.Path tempFile; + + @BeforeEach + @Override + public void before() throws IOException { + String initToken = "init_token"; + warehouse = tempFile.toUri().toString(); + restCatalogServer = new RESTCatalogServer(warehouse, initToken); + restCatalogServer.start(); + serverUrl = restCatalogServer.getUrl(); + super.before(); + sql(String.format("CREATE DATABASE %s", DATABASE_NAME)); + sql(String.format("CREATE TABLE %s.%s (a STRING, b DOUBLE)", DATABASE_NAME, TABLE_NAME)); + } + + @AfterEach() + public void after() throws IOException { + sql(String.format("DROP TABLE %s.%s", DATABASE_NAME, TABLE_NAME)); + sql(String.format("DROP DATABASE %s", DATABASE_NAME)); + restCatalogServer.shutdown(); + } + + @Test + void testCreateTable() { + List result = sql(String.format("SHOW CREATE TABLE %s.%s", DATABASE_NAME, TABLE_NAME)); + assertThat(result.toString()) + .contains( + String.format( + "CREATE TABLE `PAIMON`.`%s`.`%s` (\n" + + " `a` VARCHAR(2147483647),\n" + + " `b` DOUBLE", + DATABASE_NAME, TABLE_NAME)); + } + + @Test + void testAlterTable() { + sql(String.format("ALTER TABLE %s.%s ADD e INT AFTER b", DATABASE_NAME, TABLE_NAME)); + sql(String.format("ALTER TABLE %s.%s DROP b", DATABASE_NAME, TABLE_NAME)); + sql(String.format("ALTER TABLE %s.%s RENAME a TO a1", DATABASE_NAME, TABLE_NAME)); + sql(String.format("ALTER TABLE %s.%s MODIFY e DOUBLE", DATABASE_NAME, TABLE_NAME)); + List result = sql(String.format("SHOW CREATE TABLE %s.%s", DATABASE_NAME, TABLE_NAME)); + assertThat(result.toString()) + .contains( + String.format( + "CREATE TABLE `PAIMON`.`%s`.`%s` (\n" + + " `a1` VARCHAR(2147483647),\n" + + " `e` DOUBLE", + DATABASE_NAME, TABLE_NAME)); + } + + @Override + protected Map catalogOptions() { + String initToken = "init_token"; + Map options = new HashMap<>(); + options.put("metastore", "rest"); + options.put(RESTCatalogOptions.URI.key(), serverUrl); + options.put(RESTCatalogOptions.TOKEN.key(), initToken); + options.put(RESTCatalogOptions.THREAD_POOL_SIZE.key(), "" + 1); + return options; + } + + @Override + protected String getTempDirPath() { + return this.warehouse; + } + + @Override + protected boolean supportDefineWarehouse() { + return false; + } +} diff --git a/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java b/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java index d96fac808cab..e185e5acbf50 100644 --- a/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java +++ b/paimon-hive/paimon-hive-catalog/src/test/java/org/apache/paimon/hive/HiveCatalogTest.java @@ -172,11 +172,6 @@ private void testHiveConfDirFromEnvImpl() { assertThat(hiveConf.get("hive.metastore.uris")).isEqualTo("dummy-hms"); } - @Test - public void testAlterDatabase() throws Exception { - this.alterDatabaseWhenSupportAlter(); - } - @Test public void testAddHiveTableParameters() { try { @@ -503,4 +498,9 @@ public void testPartitionTable() throws Exception { // hive catalog list partitions from filesystem, so here return empty. assertThat(catalog.listPartitions(identifier)).isEmpty(); } + + @Override + protected boolean supportsAlterDatabase() { + return true; + } } diff --git a/pom.xml b/pom.xml index 6600f040902c..d55694cec76d 100644 --- a/pom.xml +++ b/pom.xml @@ -125,6 +125,7 @@ under the License. 1.5.5-11 3.0.11 3.4.6 + 4.12.0 2.3.1 1.3.9 2.4.9