Skip to content

Commit

Permalink
[apache#4370]feat(iceberg): support view interface for Iceberg REST s…
Browse files Browse the repository at this point in the history
…erver
  • Loading branch information
theoryxu committed Sep 26, 2024
1 parent 89b4321 commit c6f451f
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@ protected Map<String, String> getTableInfo(String tableName) {
return convertToStringMap(sql("desc table extended " + tableName));
}

protected Map<String, String> getViewInfo(String viewName) {
return convertToStringMap(sql("desc extended " + viewName));
}

protected List<String> getTableColumns(String tableName) {
List<Object[]> objects = sql("desc table extended " + tableName);
List<String> columns = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import org.apache.spark.sql.catalyst.analysis.NamespaceAlreadyExistsException;
import org.apache.spark.sql.catalyst.analysis.NoSuchNamespaceException;
import org.apache.spark.sql.catalyst.analysis.NoSuchTableException;
import org.apache.spark.sql.catalyst.analysis.NoSuchViewException;
import org.apache.spark.sql.catalyst.analysis.TableAlreadyExistsException;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
Expand Down Expand Up @@ -557,4 +558,136 @@ void testRegisterTable() {
result = convertToStringMap(sql("SELECT * FROM iceberg_rest_table_test.register_foo2"));
Assertions.assertEquals(ImmutableMap.of("1", "a", "2", "b"), result);
}

@Test
void testCreateViewAndDisplayView() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_1";
String viewName = "iceberg_rest_table_test.test_create_view";

sql(
String.format(
"CREATE TABLE %s ( id bigint, data string, ts timestamp) USING iceberg",
originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName, originTableName));

Map<String, String> viewInfo = getViewInfo(viewName);
Map<String, String> m =
ImmutableMap.of(
"id", "bigint",
"data", "string",
"ts", "timestamp");

checkMapContains(m, viewInfo);
}

@Test
void testViewProperties() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_2";
String viewName = "iceberg_rest_table_test.test_create_view_with_properties";
sql(
String.format(
"CREATE TABLE %s ( id bigint, data string, ts timestamp) USING iceberg",
originTableName));

// test create view with properties
sql(
String.format(
"CREATE VIEW %s TBLPROPERTIES ('key1' = 'val1') AS SELECT * FROM %s",
viewName, originTableName));

Map<String, String> viewInfo = getViewInfo(viewName);
Assertions.assertTrue(viewInfo.getOrDefault("View Properties", "").contains("'key1' = 'val1'"));
Assertions.assertFalse(
viewInfo.getOrDefault("View Properties", "").contains("'key2' = 'val2'"));

// test set properties
sql(
String.format(
"ALTER VIEW %s SET TBLPROPERTIES ('key1' = 'val1', 'key2' = 'val2')", viewName));

viewInfo = getViewInfo(viewName);
Assertions.assertTrue(viewInfo.getOrDefault("View Properties", "").contains("'key1' = 'val1'"));
Assertions.assertTrue(viewInfo.getOrDefault("View Properties", "").contains("'key2' = 'val2'"));

// test unset properties
sql(String.format("ALTER VIEW %s UNSET TBLPROPERTIES ('key1', 'key2')", viewName));

viewInfo = getViewInfo(viewName);
Assertions.assertFalse(
viewInfo.getOrDefault("View Properties", "").contains("'key1' = 'val1'"));
Assertions.assertFalse(
viewInfo.getOrDefault("View Properties", "").contains("'key2' = 'val2'"));
}

@Test
void testDropView() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_3";
String viewName = "iceberg_rest_table_test.test_drop_view";

sql(
String.format(
"CREATE TABLE %s ( id bigint, data string, ts timestamp) USING iceberg",
originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName, originTableName));
sql(String.format("DROP VIEW %s", viewName));

Assertions.assertThrowsExactly(AnalysisException.class, () -> getViewInfo(viewName));
Assertions.assertThrowsExactly(
NoSuchViewException.class, () -> sql(String.format("DROP VIEW %s", viewName)));
}

@Test
void testReplaceView() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_4";
String viewName = "iceberg_rest_table_test.test_replace_view";

sql(
String.format(
"CREATE TABLE %s (id bigint, data string, ts timestamp) USING iceberg",
originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName, originTableName));
sql(
String.format(
"CREATE OR REPLACE VIEW %s (updated_id COMMENT 'updated ID') TBLPROPERTIES ('key1' = 'new_val1') AS SELECT id FROM %s",
viewName, originTableName));

Map<String, String> viewInfo = getViewInfo(viewName);
Assertions.assertTrue(
viewInfo.getOrDefault("View Properties", "").contains("'key1' = 'new_val1'"));
Assertions.assertTrue(viewInfo.containsKey("updated_id"));
}

@Test
void testShowAvailableViews() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_5";
String viewName1 = "iceberg_rest_table_test.show_available_views_1";
String viewName2 = "iceberg_rest_table_test.show_available_views_2";

sql(
String.format(
"CREATE TABLE %s (id bigint, data string, ts timestamp) USING iceberg",
originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName1, originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName2, originTableName));

List<Object[]> views = sql("SHOW VIEWS IN iceberg_rest_table_test");
Assertions.assertEquals(2, views.size());
}

@Test
void testShowCreateStatementView() {
String originTableName = "iceberg_rest_table_test.create_table_for_view_6";
String viewName = "iceberg_rest_table_test.show_create_statement_view";

sql(
String.format(
"CREATE TABLE %s (id bigint, data string, ts timestamp) USING iceberg",
originTableName));
sql(String.format("CREATE VIEW %s AS SELECT * FROM %s", viewName, originTableName));

List<Object[]> result = sql(String.format("SHOW CREATE TABLE %s", viewName));
Assertions.assertEquals(1, result.size());
Assertions.assertTrue(
Arrays.stream(result.get(0)).findFirst().orElse("").toString().contains(viewName));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected Application configure() {

@ParameterizedTest
@ValueSource(strings = {"", IcebergRestTestUtil.PREFIX})
void testListTables(String prefix) {
void testListViews(String prefix) {
setUrlPathWithPrefix(prefix);
verifyListViewFail(404);

Expand Down Expand Up @@ -162,24 +162,24 @@ void testRenameTable(String prefix) {

private Response doCreateView(String name) {
CreateViewRequest createViewRequest =
ImmutableCreateViewRequest.builder()
.name(name)
.schema(viewSchema)
.viewVersion(
ImmutableViewVersion.builder()
.versionId(1)
.timestampMillis(System.currentTimeMillis())
.schemaId(1)
.defaultNamespace(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME))
.addRepresentations(
ImmutableSQLViewRepresentation.builder()
.sql(VIEW_QUERY)
.dialect("spark")
.build())
.build())
.build();
ImmutableCreateViewRequest.builder()
.name(name)
.schema(viewSchema)
.viewVersion(
ImmutableViewVersion.builder()
.versionId(1)
.timestampMillis(System.currentTimeMillis())
.schemaId(1)
.defaultNamespace(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME))
.addRepresentations(
ImmutableSQLViewRepresentation.builder()
.sql(VIEW_QUERY)
.dialect("spark")
.build())
.build())
.build();
return getViewClientBuilder()
.post(Entity.entity(createViewRequest, MediaType.APPLICATION_JSON_TYPE));
.post(Entity.entity(createViewRequest, MediaType.APPLICATION_JSON_TYPE));
}

private Response doLoadView(String name) {
Expand Down Expand Up @@ -217,20 +217,20 @@ private void verifyReplaceSucc(String name, ViewMetadata base) {
Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
LoadViewResponse loadViewResponse = response.readEntity(LoadViewResponse.class);
Assertions.assertEquals(
newViewSchema.columns(), loadViewResponse.metadata().schema().columns());
newViewSchema.columns(), loadViewResponse.metadata().schema().columns());
}

private Response doReplaceView(String name, ViewMetadata base) {
ViewMetadata.Builder builder =
ViewMetadata.buildFrom(base).setCurrentVersion(base.currentVersion(), newViewSchema);
ViewMetadata.buildFrom(base).setCurrentVersion(base.currentVersion(), newViewSchema);
ViewMetadata replacement = builder.build();
UpdateTableRequest updateTableRequest =
UpdateTableRequest.create(
null,
UpdateRequirements.forReplaceView(base, replacement.changes()),
replacement.changes());
UpdateTableRequest.create(
null,
UpdateRequirements.forReplaceView(base, replacement.changes()),
replacement.changes());
return getViewClientBuilder(Optional.of(name))
.post(Entity.entity(updateTableRequest, MediaType.APPLICATION_JSON_TYPE));
.post(Entity.entity(updateTableRequest, MediaType.APPLICATION_JSON_TYPE));
}

private ViewMetadata getViewMeta(String viewName) {
Expand Down Expand Up @@ -281,9 +281,9 @@ private void verifyLisViewSucc(Set<String> expectedTableNames) {
Assertions.assertEquals(Response.Status.OK.getStatusCode(), response.getStatus());
ListTablesResponse listTablesResponse = response.readEntity(ListTablesResponse.class);
Set<String> tableNames =
listTablesResponse.identifiers().stream()
.map(identifier -> identifier.name())
.collect(Collectors.toSet());
listTablesResponse.identifiers().stream()
.map(identifier -> identifier.name())
.collect(Collectors.toSet());
Assertions.assertEquals(expectedTableNames, tableNames);
}

Expand All @@ -294,14 +294,14 @@ private void verifyRenameViewFail(String source, String dest, int status) {

private Response doRenameView(String source, String dest) {
RenameTableRequest renameTableRequest =
RenameTableRequest.builder()
.withSource(
TableIdentifier.of(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME), source))
.withDestination(
TableIdentifier.of(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME), dest))
.build();
RenameTableRequest.builder()
.withSource(
TableIdentifier.of(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME), source))
.withDestination(
TableIdentifier.of(Namespace.of(IcebergRestTestUtil.TEST_NAMESPACE_NAME), dest))
.build();
return getRenameViewClientBuilder()
.post(Entity.entity(renameTableRequest, MediaType.APPLICATION_JSON_TYPE));
.post(Entity.entity(renameTableRequest, MediaType.APPLICATION_JSON_TYPE));
}

private void verifyRenameViewSucc(String source, String dest) {
Expand Down

0 comments on commit c6f451f

Please sign in to comment.