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 32cfd52069f0..e37e85688c18 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 @@ -75,6 +75,7 @@ import org.apache.paimon.schema.TableSchema; import org.apache.paimon.table.Table; import org.apache.paimon.table.TableSnapshot; +import org.apache.paimon.table.system.SystemTableLoader; import org.apache.paimon.utils.Pair; import org.apache.paimon.view.View; import org.apache.paimon.view.ViewImpl; @@ -286,6 +287,9 @@ public void alterDatabase(String name, List changes, boolean ign @Override public List listTables(String databaseName) throws DatabaseNotExistException { try { + if (isSystemDatabase(databaseName)) { + return SystemTableLoader.loadGlobalTableNames(); + } return listDataFromPageApi( queryParams -> client.get( @@ -465,6 +469,8 @@ public void createTable(Identifier identifier, Schema schema, boolean ignoreIfEx if (!ignoreIfExists) { throw new TableAlreadyExistException(identifier); } + } catch (NotImplementedException e) { + throw new RuntimeException(new UnsupportedOperationException(e.getMessage())); } catch (NoSuchResourceException e) { throw new DatabaseNotExistException(identifier.getDatabaseName()); } catch (BadRequestException e) { @@ -522,6 +528,8 @@ public void alterTable( throw new TableNoPermissionException(identifier, e); } catch (ServiceFailureException e) { throw new IllegalStateException(e.getMessage()); + } catch (NotImplementedException e) { + throw new UnsupportedOperationException(e.getMessage()); } catch (BadRequestException e) { throw new RuntimeException(new IllegalArgumentException(e.getMessage())); } diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/auth/DLFAuthProvider.java b/paimon-core/src/main/java/org/apache/paimon/rest/auth/DLFAuthProvider.java index 0f5371228d21..f82720b33f32 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/auth/DLFAuthProvider.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/auth/DLFAuthProvider.java @@ -89,9 +89,11 @@ public DLFAuthProvider( public Map header( Map baseHeader, RESTAuthParameter restAuthParameter) { try { - ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); - String date = now.format(AUTH_DATE_FORMATTER); - String dateTime = now.format(AUTH_DATE_TIME_FORMATTER); + String dateTime = + baseHeader.getOrDefault( + DLF_DATE_HEADER_KEY.toLowerCase(), + ZonedDateTime.now(ZoneOffset.UTC).format(AUTH_DATE_TIME_FORMATTER)); + String date = dateTime.substring(0, 8); Map signHeaders = generateSignHeaders( restAuthParameter.data(), dateTime, token.getSecurityToken()); diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/BaseResourceAuditResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/BaseResourceAuditResponse.java new file mode 100644 index 000000000000..d867efd18074 --- /dev/null +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/BaseResourceAuditResponse.java @@ -0,0 +1,88 @@ +/* + * 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; + +import org.apache.paimon.rest.RESTResponse; + +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonCreator; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonGetter; +import org.apache.paimon.shade.jackson2.com.fasterxml.jackson.annotation.JsonProperty; + +/** Base class for database, table, view, audit response. */ +public abstract class BaseResourceAuditResponse implements RESTResponse { + protected static final String FIELD_OWNER = "owner"; + protected static final String FIELD_CREATED_AT = "createdAt"; + protected static final String FIELD_CREATED_BY = "createdBy"; + protected static final String FIELD_UPDATED_AT = "updatedAt"; + protected static final String FIELD_UPDATED_BY = "updatedBy"; + + @JsonProperty(FIELD_OWNER) + private final String owner; + + @JsonProperty(FIELD_CREATED_AT) + private final long createdAt; + + @JsonProperty(FIELD_CREATED_BY) + private final String createdBy; + + @JsonProperty(FIELD_UPDATED_AT) + private final long updatedAt; + + @JsonProperty(FIELD_UPDATED_BY) + private final String updatedBy; + + @JsonCreator + public BaseResourceAuditResponse( + @JsonProperty(FIELD_OWNER) String owner, + @JsonProperty(FIELD_CREATED_AT) long createdAt, + @JsonProperty(FIELD_CREATED_BY) String createdBy, + @JsonProperty(FIELD_UPDATED_AT) long updatedAt, + @JsonProperty(FIELD_UPDATED_BY) String updatedBy) { + this.owner = owner; + this.createdAt = createdAt; + this.createdBy = createdBy; + this.updatedAt = updatedAt; + this.updatedBy = updatedBy; + } + + @JsonGetter(FIELD_OWNER) + public String getOwner() { + return owner; + } + + @JsonGetter(FIELD_CREATED_AT) + public long getCreatedAt() { + return createdAt; + } + + @JsonGetter(FIELD_CREATED_BY) + public String getCreatedBy() { + return createdBy; + } + + @JsonGetter(FIELD_UPDATED_AT) + public long getUpdatedAt() { + return updatedAt; + } + + @JsonGetter(FIELD_UPDATED_BY) + public String getUpdatedBy() { + return updatedBy; + } +} diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java index 81c706cccba2..b226f418886b 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetDatabaseResponse.java @@ -33,7 +33,8 @@ /** Response for getting database. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class GetDatabaseResponse implements RESTResponse, Database { +public class GetDatabaseResponse extends BaseResourceAuditResponse + implements RESTResponse, Database { private static final String FIELD_ID = "id"; private static final String FIELD_NAME = "name"; @@ -52,7 +53,13 @@ public class GetDatabaseResponse implements RESTResponse, Database { public GetDatabaseResponse( @JsonProperty(FIELD_ID) String id, @JsonProperty(FIELD_NAME) String name, - @JsonProperty(FIELD_OPTIONS) Map options) { + @JsonProperty(FIELD_OPTIONS) Map options, + @JsonProperty(FIELD_OWNER) String owner, + @JsonProperty(FIELD_CREATED_AT) long createdAt, + @JsonProperty(FIELD_CREATED_BY) String createdBy, + @JsonProperty(FIELD_UPDATED_AT) long updatedAt, + @JsonProperty(FIELD_UPDATED_BY) String updatedBy) { + super(owner, createdAt, createdBy, updatedAt, updatedBy); this.id = id; this.name = name; this.options = options; diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java index 82b55fae7f08..c556b591cc30 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetTableResponse.java @@ -28,7 +28,7 @@ /** Response for getting table. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class GetTableResponse implements RESTResponse { +public class GetTableResponse extends BaseResourceAuditResponse implements RESTResponse { private static final String FIELD_ID = "id"; private static final String FIELD_NAME = "name"; @@ -57,7 +57,13 @@ public GetTableResponse( @JsonProperty(FIELD_NAME) String name, @JsonProperty(FIELD_IS_EXTERNAL) boolean isExternal, @JsonProperty(FIELD_SCHEMA_ID) long schemaId, - @JsonProperty(FIELD_SCHEMA) Schema schema) { + @JsonProperty(FIELD_SCHEMA) Schema schema, + @JsonProperty(FIELD_OWNER) String owner, + @JsonProperty(FIELD_CREATED_AT) long createdAt, + @JsonProperty(FIELD_CREATED_BY) String createdBy, + @JsonProperty(FIELD_UPDATED_AT) long updatedAt, + @JsonProperty(FIELD_UPDATED_BY) String updatedBy) { + super(owner, createdAt, createdBy, updatedAt, updatedBy); this.id = id; this.name = name; this.isExternal = isExternal; diff --git a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetViewResponse.java b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetViewResponse.java index 7fe1237691b1..ab1e0341b117 100644 --- a/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetViewResponse.java +++ b/paimon-core/src/main/java/org/apache/paimon/rest/responses/GetViewResponse.java @@ -28,7 +28,7 @@ /** Response for getting view. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class GetViewResponse implements RESTResponse { +public class GetViewResponse extends BaseResourceAuditResponse implements RESTResponse { private static final String FIELD_ID = "id"; private static final String FIELD_NAME = "name"; @@ -47,7 +47,13 @@ public class GetViewResponse implements RESTResponse { public GetViewResponse( @JsonProperty(FIELD_ID) String id, @JsonProperty(FIELD_NAME) String name, - @JsonProperty(FIELD_SCHEMA) ViewSchema schema) { + @JsonProperty(FIELD_SCHEMA) ViewSchema schema, + @JsonProperty(FIELD_OWNER) String owner, + @JsonProperty(FIELD_CREATED_AT) long createdAt, + @JsonProperty(FIELD_CREATED_BY) String createdBy, + @JsonProperty(FIELD_UPDATED_AT) long updatedAt, + @JsonProperty(FIELD_UPDATED_BY) String updatedBy) { + super(owner, createdAt, createdBy, updatedAt, updatedBy); this.id = id; this.name = name; this.schema = schema; 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 77f62ed8b613..49d6f623c189 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 @@ -36,12 +36,15 @@ import org.apache.paimon.table.sink.BatchTableCommit; import org.apache.paimon.table.sink.BatchTableWrite; import org.apache.paimon.table.sink.BatchWriteBuilder; +import org.apache.paimon.table.system.AllTableOptionsTable; +import org.apache.paimon.table.system.CatalogOptionsTable; import org.apache.paimon.types.DataField; import org.apache.paimon.types.DataTypes; import org.apache.paimon.types.RowType; import org.apache.paimon.view.View; import org.apache.paimon.view.ViewImpl; +import org.apache.paimon.shade.guava30.com.google.common.collect.ImmutableMap; import org.apache.paimon.shade.guava30.com.google.common.collect.Lists; import org.apache.paimon.shade.guava30.com.google.common.collect.Maps; @@ -483,6 +486,20 @@ public void testCreateTable() throws Exception { .hasRootCauseInstanceOf(IllegalArgumentException.class) .hasRootCauseMessage( "Unrecognized option for boolean: max. Expected either true or false(case insensitive)"); + + // conflict options + Schema conflictOptionsSchema = + Schema.newBuilder() + .column("a", DataTypes.INT()) + .options(ImmutableMap.of("changelog-producer", "input")) + .build(); + assertThatThrownBy( + () -> + catalog.createTable( + Identifier.create("test_db", "conflict_options_table"), + conflictOptionsSchema, + false)) + .isInstanceOf(RuntimeException.class); } @Test @@ -538,6 +555,12 @@ public void testGetTable() throws Exception { assertThatExceptionOfType(Catalog.TableNotExistException.class) .isThrownBy( () -> catalog.getTable(Identifier.create(SYSTEM_DATABASE_NAME, "1111"))); + + List sysTables = catalog.listTables(SYSTEM_DATABASE_NAME); + assertThat(sysTables) + .containsExactlyInAnyOrder( + AllTableOptionsTable.ALL_TABLE_OPTIONS, + CatalogOptionsTable.CATALOG_OPTIONS); } @Test @@ -683,6 +706,19 @@ public void testAlterTable() throws Exception { anyCauseMatches( Catalog.ColumnAlreadyExistException.class, "Column col1 already exists in the test_db.test_table table.")); + + // conflict options + assertThatThrownBy( + () -> + catalog.alterTable( + identifier, + Lists.newArrayList( + SchemaChange.setOption( + "changelog-producer", "input")), + false)) + .isInstanceOf(RuntimeException.class) + .hasMessageContaining( + "Can not set changelog-producer on table without primary keys"); } @Test 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 1470b5200150..3c099f737215 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 @@ -82,7 +82,15 @@ public static GetDatabaseResponse getDatabaseResponse(String name) { Map options = new HashMap<>(); options.put("a", "b"); options.put(COMMENT_PROP, "comment"); - return new GetDatabaseResponse(UUID.randomUUID().toString(), name, options); + return new GetDatabaseResponse( + UUID.randomUUID().toString(), + name, + options, + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } public static ListDatabasesResponse listDatabasesResponse(String name) { @@ -219,7 +227,17 @@ public static GetTableResponse getTableResponse() { Map options = new HashMap<>(); options.put("option-1", "value-1"); options.put("option-2", "value-2"); - return new GetTableResponse(UUID.randomUUID().toString(), "", false, 1, schema(options)); + return new GetTableResponse( + UUID.randomUUID().toString(), + "", + false, + 1, + schema(options), + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } public static CreateViewRequest createViewRequest(String name) { @@ -228,7 +246,15 @@ public static CreateViewRequest createViewRequest(String name) { } public static GetViewResponse getViewResponse() { - return new GetViewResponse(UUID.randomUUID().toString(), "", viewSchema()); + return new GetViewResponse( + UUID.randomUUID().toString(), + "", + viewSchema(), + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } public static ListViewsResponse listViewsResponse() { 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 index e285a5facd06..b9d667a59d91 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java @@ -272,10 +272,6 @@ public MockResponse dispatch(RecordedRequest request) { resources.length == 2 && resources[1].startsWith("tables"); boolean isTableDetails = resources.length == 2 && resources[1].startsWith("table-details"); - boolean isViewRename = - resources.length == 3 - && "views".equals(resources[1]) - && "rename".equals(resources[2]); boolean isView = resources.length == 3 && "views".equals(resources[1]) @@ -484,14 +480,18 @@ public MockResponse dispatch(RecordedRequest request) { new ErrorResponse( null, null, e.getCause().getCause().getMessage(), 400); return mockResponse(response, 400); - } else if (e instanceof UnsupportedOperationException) { + } else if (e instanceof UnsupportedOperationException + || e.getCause() instanceof UnsupportedOperationException) { response = new ErrorResponse(null, null, e.getMessage(), 501); return mockResponse(response, 501); - } else if (e instanceof IllegalStateException) { + } else if (e instanceof IllegalStateException + || e.getCause() instanceof IllegalStateException) { response = new ErrorResponse(null, null, e.getMessage(), 500); return mockResponse(response, 500); } - return new MockResponse().setResponseCode(500); + return new MockResponse() + .setResponseCode(500) + .setBody(e.getCause().getMessage()); } } }; @@ -678,7 +678,12 @@ private MockResponse databaseHandle(String method, String data, String databaseN new GetDatabaseResponse( UUID.randomUUID().toString(), database.name(), - database.options()); + database.options(), + "owner", + 1L, + "created", + 1L, + "updated"); return mockResponse(response, 200); case "DELETE": catalog.dropDatabase(databaseName, false, true); @@ -747,7 +752,7 @@ private MockResponse tablesHandle( tableMetadata = createTableMetadata( requestBody.getIdentifier(), - 1L, + 0L, requestBody.getSchema(), UUID.randomUUID().toString(), false); @@ -849,7 +854,12 @@ private List listTableDetails(String databaseName) { identifier.getTableName(), entry.getValue().isExternal(), entry.getValue().schema().id(), - entry.getValue().schema().toSchema()); + entry.getValue().schema().toSchema(), + "owner", + 1L, + "created", + 1L, + "updated"); tableDetails.add(getTableResponse); } } @@ -869,7 +879,6 @@ private MockResponse tableHandle(String method, String data, Identifier identifi switch (method) { case "GET": TableMetadata tableMetadata; - identifier.isSystemTable(); if (identifier.isSystemTable()) { TableSchema schema = catalog.loadTableSchema(identifier); tableMetadata = @@ -884,7 +893,12 @@ private MockResponse tableHandle(String method, String data, Identifier identifi identifier.getTableName(), tableMetadata.isExternal(), tableMetadata.schema().id(), - tableMetadata.schema().toSchema()); + tableMetadata.schema().toSchema(), + "owner", + 1L, + "created", + 1L, + "updated"); return mockResponse(response, 200); case "POST": AlterTableRequest requestBody = @@ -898,6 +912,8 @@ private MockResponse tableHandle(String method, String data, Identifier identifi System.out.println(e.getMessage()); } tableMetadataStore.remove(identifier.getFullName()); + tableSnapshotStore.remove(identifier.getFullName()); + tablePartitionsStore.remove(identifier.getFullName()); return new MockResponse().setResponseCode(200); default: return new MockResponse().setResponseCode(404); @@ -1178,7 +1194,15 @@ private List listViewDetails(String databaseName) { view.dialects(), view.comment().orElse(null), view.options()); - return new GetViewResponse("id", identifier.getTableName(), schema); + return new GetViewResponse( + "id", + identifier.getTableName(), + schema, + "owner", + 1L, + "created", + 1L, + "updated"); }) .collect(Collectors.toList()); } @@ -1197,7 +1221,16 @@ private MockResponse viewHandle(String method, Identifier identifier) throws Exc view.dialects(), view.comment().orElse(null), view.options()); - response = new GetViewResponse("id", identifier.getTableName(), schema); + response = + new GetViewResponse( + "id", + identifier.getTableName(), + schema, + "owner", + 1L, + "created", + 1L, + "updated"); return mockResponse(response, 200); } throw new Catalog.ViewNotExistException(identifier); @@ -1346,11 +1379,6 @@ private TableMetadata createFormatTable(Identifier identifier, Schema schema) { return createTableMetadata(identifier, 1L, schema, UUID.randomUUID().toString(), true); } - private Partition spec2Partition(Map spec) { - // todo: need update - return new Partition(spec, 123, 456, 789, 123, false); - } - private FileStoreTable getFileTable(Identifier identifier) throws Catalog.TableNotExistException { if (tableMetadataStore.containsKey(identifier.getFullName())) { diff --git a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java index e7d08e2c0b9a..dd82c8cefa3c 100644 --- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java +++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTestBase.java @@ -843,6 +843,20 @@ void testSnapshotFromREST() throws Exception { assertThat(snapshot.get().fileSizeInBytes()).isEqualTo(2); assertThat(snapshot.get().fileCount()).isEqualTo(3); assertThat(snapshot.get().lastFileCreationTime()).isEqualTo(4); + + // drop table then create table + catalog.dropTable(hasSnapshotTableIdentifier, true); + createTable(hasSnapshotTableIdentifier, Maps.newHashMap(), Lists.newArrayList("col1")); + snapshot = catalog.loadSnapshot(hasSnapshotTableIdentifier); + assertThat(snapshot).isEmpty(); + updateSnapshotOnRestServer( + hasSnapshotTableIdentifier, createSnapshotWithMillis(id, millis), 5, 6, 7, 8); + snapshot = catalog.loadSnapshot(hasSnapshotTableIdentifier); + assertThat(snapshot.get().recordCount()).isEqualTo(5); + + // test no snapshot + catalog.loadSnapshot(hasSnapshotTableIdentifier); + createTable(hasSnapshotTableIdentifier, Maps.newHashMap(), Lists.newArrayList("col1")); Identifier noSnapshotTableIdentifier = Identifier.create("test_db_a_1", "unknown"); createTable(noSnapshotTableIdentifier, Maps.newHashMap(), Lists.newArrayList("col1")); snapshot = catalog.loadSnapshot(noSnapshotTableIdentifier); diff --git a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java index 8cd6afbb4dce..3be5071e7abd 100644 --- a/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java +++ b/paimon-flink/paimon-flink-common/src/test/java/org/apache/paimon/flink/CatalogTableITCase.java @@ -552,14 +552,14 @@ public void testConflictOption() { assertThatThrownBy( () -> sql("CREATE TABLE T (a INT) WITH ('changelog-producer' = 'input')")) .rootCause() - .isInstanceOf(UnsupportedOperationException.class) + .isInstanceOf(RuntimeException.class) .hasMessageContaining( "Can not set changelog-producer on table without primary keys"); sql("CREATE TABLE T (a INT)"); assertThatThrownBy(() -> sql("ALTER TABLE T SET ('changelog-producer'='input')")) .rootCause() - .isInstanceOf(UnsupportedOperationException.class) + .isInstanceOf(RuntimeException.class) .hasMessageContaining( "Can not set changelog-producer on table without primary keys"); } diff --git a/paimon-open-api/rest-catalog-open-api.yaml b/paimon-open-api/rest-catalog-open-api.yaml index fe10e71c3e8a..4183e2874124 100644 --- a/paimon-open-api/rest-catalog-open-api.yaml +++ b/paimon-open-api/rest-catalog-open-api.yaml @@ -1219,6 +1219,16 @@ components: $ref: '#/components/schemas/Schema' uuid: type: string + owner: + type: string + createdAt: + format: int64 + createdBy: + type: string + updatedAt: + format: int64 + updatedBy: + type: string SchemaChange: anyOf: - $ref: '#/components/schemas/SetOption' @@ -1509,6 +1519,16 @@ components: type: object additionalProperties: type: string + owner: + type: string + createdAt: + format: int64 + createdBy: + type: string + updatedAt: + format: int64 + updatedBy: + type: string ListTablesResponse: type: object properties: @@ -1576,6 +1596,16 @@ components: type: string schema: $ref: '#/components/schemas/ViewSchema' + owner: + type: string + createdAt: + format: int64 + createdBy: + type: string + updatedAt: + format: int64 + updatedBy: + type: string ListViewsResponse: type: object properties: diff --git a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java index 0ca57e0bde9c..faaa260bdd31 100644 --- a/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java +++ b/paimon-open-api/src/main/java/org/apache/paimon/open/api/RESTCatalogController.java @@ -164,7 +164,15 @@ public CreateDatabaseResponse createDatabases( public GetDatabaseResponse getDatabases( @PathVariable String prefix, @PathVariable String database) { Map options = new HashMap<>(); - return new GetDatabaseResponse(UUID.randomUUID().toString(), "name", options); + return new GetDatabaseResponse( + UUID.randomUUID().toString(), + "name", + options, + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } @Operation( @@ -267,7 +275,12 @@ public ListTableDetailsResponse listTableDetails( ImmutableList.of(), ImmutableList.of(), new HashMap<>(), - "test-comment")); + "test-comment"), + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); return new ListTableDetailsResponse(ImmutableList.of(singleTable), null); } @@ -305,7 +318,12 @@ public GetTableResponse getTable( ImmutableList.of(), ImmutableList.of(), new HashMap<>(), - "comment")); + "comment"), + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } @Operation( @@ -640,7 +658,16 @@ public ListViewDetailsResponse listViewDetails( Collections.emptyMap(), "comment", Collections.singletonMap("pt", "1")); - GetViewResponse singleView = new GetViewResponse("id", "name", schema); + GetViewResponse singleView = + new GetViewResponse( + "id", + "name", + schema, + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); return new ListViewDetailsResponse(ImmutableList.of(singleView), null); } @@ -692,7 +719,15 @@ public GetViewResponse getView( Collections.emptyMap(), "comment", Collections.singletonMap("pt", "1")); - return new GetViewResponse("id", "name", schema); + return new GetViewResponse( + "id", + "name", + schema, + "owner", + System.currentTimeMillis(), + "created", + System.currentTimeMillis(), + "updated"); } @Operation( diff --git a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadITCase.java b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadITCase.java index b00267410a7f..a96954dcdf8e 100644 --- a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadITCase.java +++ b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkReadITCase.java @@ -312,7 +312,7 @@ public void testConflictOption() { spark.sql( "CREATE TABLE T (a INT) TBLPROPERTIES ('changelog-producer' = 'input')")) .rootCause() - .isInstanceOf(UnsupportedOperationException.class) + .isInstanceOf(RuntimeException.class) .hasMessageContaining( "Can not set changelog-producer on table without primary keys"); @@ -323,7 +323,7 @@ public void testConflictOption() { spark.sql( "ALTER TABLE T SET TBLPROPERTIES('changelog-producer' 'input')")) .rootCause() - .isInstanceOf(UnsupportedOperationException.class) + .isInstanceOf(RuntimeException.class) .hasMessageContaining( "Can not set changelog-producer on table without primary keys"); }