Skip to content

Commit f65fda5

Browse files
matar993Marco Scalzoagilelab-tmnd1991
authored
implement getMetadata api (#188)
Co-authored-by: Marco Scalzo <[email protected]> Co-authored-by: Antonio Murgia <[email protected]>
1 parent aa60a89 commit f65fda5

File tree

6 files changed

+232
-11
lines changed

6 files changed

+232
-11
lines changed

server/app/src/test/java/io/whitefox/api/deltasharing/SampleTables.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.whitefox.api.deltasharing;
22

33
import static io.whitefox.DeltaTestUtils.*;
4+
import static io.whitefox.IcebergTestUtils.icebergTableWithHadoopCatalog;
5+
import static io.whitefox.IcebergTestUtils.s3IcebergTableWithAwsGlueCatalog;
46

7+
import io.whitefox.AwsGlueTestConfig;
58
import io.whitefox.S3TestConfig;
69
import io.whitefox.api.deltasharing.model.FileObjectFileWithoutPresignedUrl;
710
import io.whitefox.api.deltasharing.model.FileObjectWithoutPresignedUrl;
@@ -27,8 +30,17 @@ public static InternalTable s3DeltaTableWithHistory1(S3TestConfig s3TestConfig)
2730
return s3DeltaTable("delta-table-with-history", s3TestConfig);
2831
}
2932

33+
public static InternalTable s3IcebergTable1(
34+
S3TestConfig s3TestConfig, AwsGlueTestConfig awsGlueTestConfig) {
35+
return s3IcebergTableWithAwsGlueCatalog(
36+
s3TestConfig, awsGlueTestConfig, "test_glue_db", "icebergtable1");
37+
}
38+
3039
public static final InternalTable deltaTable1 = deltaTable("delta-table");
3140

41+
public static final InternalTable icebergtable1 =
42+
icebergTableWithHadoopCatalog("test_db", "icebergtable1");
43+
3244
public static final String deltaTable1Path = deltaTableUri("delta-table");
3345

3446
public static final String deltaTableWithHistory1Path = deltaTableUri("delta-table-with-history");
@@ -46,7 +58,8 @@ public static StorageManager createStorageManager() {
4658
List.of(
4759
new SharedTable("table1", "default", "name", deltaTable1),
4860
new SharedTable(
49-
"table-with-history", "default", "name", deltaTableWithHistory1)),
61+
"table-with-history", "default", "name", deltaTableWithHistory1),
62+
new SharedTable("icebergtable1", "default", "name", icebergtable1)),
5063
"name")),
5164
testPrincipal,
5265
0L)));

server/app/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplAwsTest.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.quarkus.test.junit.QuarkusMock;
1010
import io.quarkus.test.junit.QuarkusTest;
1111
import io.restassured.http.Header;
12+
import io.whitefox.AwsGlueTestConfig;
1213
import io.whitefox.S3TestConfig;
1314
import io.whitefox.api.OpenApiValidatorUtils;
1415
import io.whitefox.api.deltasharing.SampleTables;
@@ -51,14 +52,18 @@ public static void setup() {
5152

5253
private final S3TestConfig s3TestConfig;
5354

55+
private final AwsGlueTestConfig awsGlueTestConfig;
56+
5457
@Inject
55-
public DeltaSharesApiImplAwsTest(ObjectMapper objectMapper, S3TestConfig s3TestConfig) {
58+
public DeltaSharesApiImplAwsTest(
59+
ObjectMapper objectMapper, S3TestConfig s3TestConfig, AwsGlueTestConfig awsGlueTestConfig) {
5660
this.objectMapper = objectMapper;
5761
this.s3TestConfig = s3TestConfig;
62+
this.awsGlueTestConfig = awsGlueTestConfig;
5863
}
5964

6065
@BeforeEach
61-
public void updateStorageManagerWithS3DeltaTables() {
66+
public void updateStorageManagerWithS3Tables() {
6267
storageManager.createShare(new Share(
6368
"s3share",
6469
"key",
@@ -72,12 +77,51 @@ public void updateStorageManagerWithS3DeltaTables() {
7277
"s3table-with-history",
7378
"s3schema",
7479
"s3share",
75-
s3DeltaTableWithHistory1(s3TestConfig))),
80+
s3DeltaTableWithHistory1(s3TestConfig)),
81+
new SharedTable(
82+
"s3IcebergTable1",
83+
"s3schema",
84+
"s3share",
85+
s3IcebergTable1(s3TestConfig, awsGlueTestConfig))),
7686
"s3share")),
7787
new Principal("Mr fox"),
7888
0L));
7989
}
8090

91+
@Test
92+
@DisabledOnOs(OS.WINDOWS)
93+
public void icebergTableMetadata() throws IOException {
94+
var responseBodyLines = given()
95+
.when()
96+
.filter(deltaFilter)
97+
.get(
98+
"delta-api/v1/shares/{share}/schemas/{schema}/tables/{table}/metadata",
99+
"s3share",
100+
"s3schema",
101+
"s3IcebergTable1")
102+
.then()
103+
.statusCode(200)
104+
.extract()
105+
.asString()
106+
.split("\n");
107+
assertEquals(2, responseBodyLines.length);
108+
assertEquals(
109+
new ProtocolObject().protocol(new ProtocolObjectProtocol().minReaderVersion(1)),
110+
objectMapper.reader().readValue(responseBodyLines[0], ProtocolObject.class));
111+
assertEquals(
112+
new MetadataObject()
113+
.metaData(new MetadataObjectMetaData()
114+
.id("7819530050735196523")
115+
.name("metastore.test_glue_db.icebergtable1")
116+
.format(new FormatObject().provider("parquet"))
117+
.schemaString(
118+
"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"long\",\"nullable\":false,\"metadata\":{}}]}")
119+
.partitionColumns(List.of())
120+
.version(1L)
121+
._configuration(Map.of("write.parquet.compression-codec", "zstd"))),
122+
objectMapper.reader().readValue(responseBodyLines[1], MetadataObject.class));
123+
}
124+
81125
@DisabledOnOs(OS.WINDOWS)
82126
@Test
83127
public void queryTableCurrentVersion() throws IOException {

server/app/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplTest.java

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,10 @@ public void listTables() {
149149
.get("delta-api/v1/shares/{share}/schemas/{schema}/tables", "name", "default")
150150
.then()
151151
.statusCode(200)
152-
.body("items", hasSize(2))
153-
.body("items[0].name", either(is("table1")).or(is("table-with-history")))
152+
.body("items", hasSize(3))
153+
.body(
154+
"items[0].name",
155+
either(is("table1")).or(is("table-with-history")).or(is("icebergtable1")))
154156
.body("items[0].schema", is("default"))
155157
.body("items[0].share", is("name"))
156158
.body("nextPageToken", is(nullValue()));
@@ -172,7 +174,7 @@ public void tableMetadataNotFound() {
172174

173175
@Test
174176
@DisabledOnOs(OS.WINDOWS)
175-
public void tableMetadata() throws IOException {
177+
public void deltaTableMetadata() throws IOException {
176178
var responseBodyLines = given()
177179
.when()
178180
.filter(deltaFilter)
@@ -204,6 +206,40 @@ public void tableMetadata() throws IOException {
204206
objectMapper.reader().readValue(responseBodyLines[1], MetadataObject.class));
205207
}
206208

209+
@Test
210+
@DisabledOnOs(OS.WINDOWS)
211+
public void icebergTableMetadata() throws IOException {
212+
var responseBodyLines = given()
213+
.when()
214+
.filter(deltaFilter)
215+
.get(
216+
"delta-api/v1/shares/{share}/schemas/{schema}/tables/{table}/metadata",
217+
"name",
218+
"default",
219+
"icebergtable1")
220+
.then()
221+
.statusCode(200)
222+
.extract()
223+
.asString()
224+
.split("\n");
225+
assertEquals(2, responseBodyLines.length);
226+
assertEquals(
227+
new ProtocolObject().protocol(new ProtocolObjectProtocol().minReaderVersion(1)),
228+
objectMapper.reader().readValue(responseBodyLines[0], ProtocolObject.class));
229+
assertEquals(
230+
new MetadataObject()
231+
.metaData(new MetadataObjectMetaData()
232+
.id("3369848726892806393")
233+
.name("metastore.test_db.icebergtable1")
234+
.format(new FormatObject().provider("parquet"))
235+
.schemaString(
236+
"{\"type\":\"struct\",\"fields\":[{\"name\":\"id\",\"type\":\"long\",\"nullable\":false,\"metadata\":{}}]}")
237+
.partitionColumns(List.of())
238+
.version(1L)
239+
._configuration(Map.of("write.parquet.compression-codec", "zstd"))),
240+
objectMapper.reader().readValue(responseBodyLines[1], MetadataObject.class));
241+
}
242+
207243
@Test
208244
public void listAllTables() {
209245
given()
@@ -212,8 +248,10 @@ public void listAllTables() {
212248
.get("delta-api/v1/shares/{share}/all-tables", "name")
213249
.then()
214250
.statusCode(200)
215-
.body("items", hasSize(2))
216-
.body("items[0].name", either(is("table1")).or(is("table-with-history")))
251+
.body("items", hasSize(3))
252+
.body(
253+
"items[0].name",
254+
either(is("table1")).or(is("table-with-history")).or(is("icebergtable1")))
217255
.body("items[0].schema", is("default"))
218256
.body("items[0].share", is("name"))
219257
.body("nextPageToken", is(nullValue()));

server/core/src/main/java/io/whitefox/core/services/IcebergSharedTable.java

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33
import io.whitefox.core.Metadata;
44
import io.whitefox.core.ReadTableRequest;
55
import io.whitefox.core.ReadTableResultToBeSigned;
6+
import io.whitefox.core.TableSchema;
7+
import java.sql.Timestamp;
8+
import java.time.OffsetDateTime;
9+
import java.time.format.DateTimeFormatter;
610
import java.util.Optional;
11+
import java.util.stream.Collectors;
712
import org.apache.commons.lang3.NotImplementedException;
13+
import org.apache.iceberg.PartitionField;
14+
import org.apache.iceberg.Snapshot;
815
import org.apache.iceberg.Table;
916

1017
public class IcebergSharedTable implements InternalSharedTable {
@@ -27,12 +34,44 @@ public static IcebergSharedTable of(Table icebergTable) {
2734
}
2835

2936
public Optional<Metadata> getMetadata(Optional<String> startingTimestamp) {
30-
throw new NotImplementedException();
37+
return getSnapshot(startingTimestamp).map(this::getMetadataFromSnapshot);
38+
}
39+
40+
private Metadata getMetadataFromSnapshot(Snapshot snapshot) {
41+
return new Metadata(
42+
String.valueOf(snapshot.snapshotId()),
43+
Optional.of(icebergTable.name()),
44+
Optional.empty(),
45+
Metadata.Format.PARQUET,
46+
new TableSchema(tableSchemaConverter.convertIcebergSchemaToWhitefox(
47+
icebergTable.schema().asStruct())),
48+
icebergTable.spec().fields().stream()
49+
.map(PartitionField::name)
50+
.collect(Collectors.toList()),
51+
icebergTable.properties(),
52+
Optional.of(snapshot.sequenceNumber()),
53+
Optional.empty(), // size is fine to be empty
54+
Optional.empty() // numFiles is ok to be empty here too
55+
);
56+
}
57+
58+
private Optional<Snapshot> getSnapshot(Optional<String> startingTimestamp) {
59+
return startingTimestamp
60+
.map(this::getTimestamp)
61+
.map(Timestamp::getTime)
62+
.map(icebergTable::snapshot)
63+
.or(() -> Optional.of(icebergTable.currentSnapshot()));
64+
}
65+
66+
private Timestamp getTimestamp(String timestamp) {
67+
return new Timestamp(OffsetDateTime.parse(timestamp, DateTimeFormatter.ISO_OFFSET_DATE_TIME)
68+
.toInstant()
69+
.toEpochMilli());
3170
}
3271

3372
@Override
3473
public Optional<Long> getTableVersion(Optional<String> startingTimestamp) {
35-
throw new NotImplementedException();
74+
return Optional.of(0L);
3675
}
3776

3877
@Override
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.whitefox.core.services;
2+
3+
import static io.whitefox.IcebergTestUtils.s3IcebergTableWithAwsGlueCatalog;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.wildfly.common.Assert.assertTrue;
7+
8+
import io.whitefox.AwsGlueTestConfig;
9+
import io.whitefox.S3TestConfig;
10+
import io.whitefox.core.SharedTable;
11+
import java.util.Optional;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.condition.DisabledOnOs;
14+
import org.junit.jupiter.api.condition.OS;
15+
16+
@DisabledOnOs(OS.WINDOWS)
17+
public class IcebergAwsSharedTableTest {
18+
19+
private final IcebergTableLoader icebergTableLoader = new IcebergTableLoader(
20+
new IcebergCatalogHandler(new AwsGlueConfigBuilder(), new HadoopConfigBuilder()));
21+
private final S3TestConfig s3TestConfig = S3TestConfig.loadFromEnv();
22+
private final AwsGlueTestConfig awsGlueTestConfig = AwsGlueTestConfig.loadFromEnv();
23+
24+
@Test
25+
void getTableMetadata() {
26+
var PTable = new SharedTable(
27+
"icebergtable1",
28+
"default",
29+
"share1",
30+
s3IcebergTableWithAwsGlueCatalog(
31+
s3TestConfig, awsGlueTestConfig, "test_glue_db", "icebergtable1"));
32+
var DTable = icebergTableLoader.loadTable(PTable);
33+
var metadata = DTable.getMetadata(Optional.empty());
34+
assertTrue(metadata.isPresent());
35+
assertEquals("7819530050735196523", metadata.get().id());
36+
}
37+
38+
@Test
39+
void getUnknownTableMetadata() {
40+
var unknownPTable = new SharedTable(
41+
"notFound",
42+
"default",
43+
"share1",
44+
s3IcebergTableWithAwsGlueCatalog(
45+
s3TestConfig, awsGlueTestConfig, "test_glue_db", "not-found"));
46+
assertThrows(IllegalArgumentException.class, () -> DeltaSharedTable.of(unknownPTable));
47+
}
48+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package io.whitefox.core.services;
2+
3+
import static io.whitefox.IcebergTestUtils.icebergTableWithHadoopCatalog;
4+
import static org.junit.jupiter.api.Assertions.assertEquals;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
6+
import static org.wildfly.common.Assert.assertTrue;
7+
8+
import io.whitefox.core.SharedTable;
9+
import java.util.Optional;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.api.condition.DisabledOnOs;
12+
import org.junit.jupiter.api.condition.OS;
13+
14+
@DisabledOnOs(OS.WINDOWS)
15+
public class IcebergSharedTableTest {
16+
17+
private final IcebergTableLoader icebergTableLoader = new IcebergTableLoader(
18+
new IcebergCatalogHandler(new AwsGlueConfigBuilder(), new HadoopConfigBuilder()));
19+
20+
@Test
21+
void getTableMetadata() {
22+
var PTable = new SharedTable(
23+
"icebergtable1",
24+
"default",
25+
"share1",
26+
icebergTableWithHadoopCatalog("test_db", "icebergtable1"));
27+
var DTable = icebergTableLoader.loadTable(PTable);
28+
var metadata = DTable.getMetadata(Optional.empty());
29+
assertTrue(metadata.isPresent());
30+
assertEquals("3369848726892806393", metadata.get().id());
31+
}
32+
33+
@Test
34+
void getUnknownTableMetadata() {
35+
var unknownPTable = new SharedTable(
36+
"notFound", "default", "share1", icebergTableWithHadoopCatalog("test_db", "not-found"));
37+
assertThrows(IllegalArgumentException.class, () -> DeltaSharedTable.of(unknownPTable));
38+
}
39+
}

0 commit comments

Comments
 (0)