diff --git a/docs/protocol/whitefox-protocol-api.yml b/docs/protocol/whitefox-protocol-api.yml index 4bc538383..7de4f6ec8 100644 --- a/docs/protocol/whitefox-protocol-api.yml +++ b/docs/protocol/whitefox-protocol-api.yml @@ -997,7 +997,13 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/TableReference" + type: object + properties: + name: + type: string + description: public name of the table; can be different from the internal name used in `reference` + reference: + $ref: "#/components/schemas/TableReference" responses: "200": description: table added to schema diff --git a/server/src/main/java/io/whitefox/api/server/ShareV1ApiImpl.java b/server/src/main/java/io/whitefox/api/server/ShareV1ApiImpl.java index b509909dd..6febea395 100644 --- a/server/src/main/java/io/whitefox/api/server/ShareV1ApiImpl.java +++ b/server/src/main/java/io/whitefox/api/server/ShareV1ApiImpl.java @@ -1,9 +1,10 @@ package io.whitefox.api.server; import io.whitefox.api.model.v1.generated.AddRecipientToShareRequest; +import io.whitefox.api.model.v1.generated.AddTableToSchemaRequest; import io.whitefox.api.model.v1.generated.CreateShareInput; -import io.whitefox.api.model.v1.generated.TableReference; import io.whitefox.api.server.v1.generated.ShareV1Api; +import io.whitefox.core.*; import io.whitefox.core.services.ShareService; import jakarta.ws.rs.core.Response; @@ -29,8 +30,19 @@ public Response addRecipientToShare( } @Override - public Response addTableToSchema(String share, String schema, TableReference tableReference) { - return Response.status(501).build(); + public Response addTableToSchema( + String share, String schema, AddTableToSchemaRequest addTableToSchemaRequest) { + return wrapExceptions( + () -> Response.status(Response.Status.CREATED) + .entity(WhitefoxMappers.share2api(shareService.addTableToSchema( + new ShareName(share), + new SchemaName(schema), + new SharedTableName(addTableToSchemaRequest.getName()), + new ProviderName(addTableToSchemaRequest.getReference().getProviderName()), + new InternalTableName(addTableToSchemaRequest.getReference().getName()), + getRequestPrincipal()))) + .build(), + exceptionToResponse); } @Override diff --git a/server/src/main/java/io/whitefox/core/InternalTableName.java b/server/src/main/java/io/whitefox/core/InternalTableName.java new file mode 100644 index 000000000..244e006fe --- /dev/null +++ b/server/src/main/java/io/whitefox/core/InternalTableName.java @@ -0,0 +1,37 @@ +package io.whitefox.core; + +import io.whitefox.annotations.SkipCoverageGenerated; +import java.util.Objects; + +public class InternalTableName { + private final String name; + + public InternalTableName(String name) { + this.name = name; + } + + @Override + @SkipCoverageGenerated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + InternalTableName that = (InternalTableName) o; + return Objects.equals(name, that.name); + } + + @Override + @SkipCoverageGenerated + public int hashCode() { + return Objects.hash(name); + } + + public String name() { + return name; + } + + @Override + @SkipCoverageGenerated + public String toString() { + return name; + } +} diff --git a/server/src/main/java/io/whitefox/core/ProviderName.java b/server/src/main/java/io/whitefox/core/ProviderName.java new file mode 100644 index 000000000..6e0d3d3f8 --- /dev/null +++ b/server/src/main/java/io/whitefox/core/ProviderName.java @@ -0,0 +1,37 @@ +package io.whitefox.core; + +import io.whitefox.annotations.SkipCoverageGenerated; +import java.util.Objects; + +public class ProviderName { + private final String name; + + public ProviderName(String share) { + this.name = share; + } + + public String name() { + return name; + } + + @Override + @SkipCoverageGenerated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProviderName that = (ProviderName) o; + return Objects.equals(name, that.name); + } + + @Override + @SkipCoverageGenerated + public int hashCode() { + return Objects.hash(name); + } + + @Override + @SkipCoverageGenerated + public String toString() { + return name; + } +} diff --git a/server/src/main/java/io/whitefox/core/Schema.java b/server/src/main/java/io/whitefox/core/Schema.java index 91db75cf6..d24571b4c 100644 --- a/server/src/main/java/io/whitefox/core/Schema.java +++ b/server/src/main/java/io/whitefox/core/Schema.java @@ -1,15 +1,23 @@ package io.whitefox.core; import io.whitefox.annotations.SkipCoverageGenerated; -import java.util.List; -import java.util.Objects; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; public final class Schema { private final String name; - private final List sharedTables; + private final Map sharedTables; private final String share; - public Schema(String name, List sharedTables, String share) { + public Schema(String name, Collection sharedTables, String share) { + this( + name, + sharedTables.stream().collect(Collectors.toMap(SharedTable::name, Function.identity())), + share); + } + + public Schema(String name, Map sharedTables, String share) { this.name = name; this.sharedTables = sharedTables; this.share = share; @@ -19,8 +27,8 @@ public String name() { return name; } - public List tables() { - return sharedTables; + public Set tables() { + return Set.copyOf(sharedTables.values()); } public String share() { @@ -50,4 +58,11 @@ public String toString() { return "Schema[" + "name=" + name + ", " + "tables=" + sharedTables + ", " + "share=" + share + ']'; } + + public Schema addTable(InternalTable table, SharedTableName sharedTableName) { + var newSharedTables = new HashMap<>(sharedTables); + newSharedTables.put( + sharedTableName.name(), new SharedTable(sharedTableName.name(), this.name, share, table)); + return new Schema(this.name, newSharedTables, share); + } } diff --git a/server/src/main/java/io/whitefox/core/SchemaName.java b/server/src/main/java/io/whitefox/core/SchemaName.java new file mode 100644 index 000000000..1f1b6c27b --- /dev/null +++ b/server/src/main/java/io/whitefox/core/SchemaName.java @@ -0,0 +1,37 @@ +package io.whitefox.core; + +import io.whitefox.annotations.SkipCoverageGenerated; +import java.util.Objects; + +public class SchemaName { + private final String name; + + public SchemaName(String name) { + this.name = name; + } + + public String name() { + return name; + } + + @Override + @SkipCoverageGenerated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SchemaName that = (SchemaName) o; + return Objects.equals(name, that.name); + } + + @Override + @SkipCoverageGenerated + public int hashCode() { + return Objects.hash(name); + } + + @Override + @SkipCoverageGenerated + public String toString() { + return name; + } +} diff --git a/server/src/main/java/io/whitefox/core/Share.java b/server/src/main/java/io/whitefox/core/Share.java index 26e38a78c..e26a3fb6d 100644 --- a/server/src/main/java/io/whitefox/core/Share.java +++ b/server/src/main/java/io/whitefox/core/Share.java @@ -114,7 +114,7 @@ public Share addRecipients(List recipients, Principal currentUser, lo owner); } - public Share addSchema(Schema schema, Principal currentUser, long now) { + public Share upsertSchema(Schema schema, Principal currentUser, long now) { var newSchemas = new HashMap<>(schemas); newSchemas.put(schema.name(), schema); return new Share( diff --git a/server/src/main/java/io/whitefox/core/ShareName.java b/server/src/main/java/io/whitefox/core/ShareName.java new file mode 100644 index 000000000..30a30682c --- /dev/null +++ b/server/src/main/java/io/whitefox/core/ShareName.java @@ -0,0 +1,38 @@ +package io.whitefox.core; + +import io.whitefox.annotations.SkipCoverageGenerated; +import java.util.Objects; + +public final class ShareName { + + private final String name; + + public ShareName(String share) { + this.name = share; + } + + public String name() { + return name; + } + + @Override + @SkipCoverageGenerated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ShareName shareName = (ShareName) o; + return Objects.equals(name, shareName.name); + } + + @Override + @SkipCoverageGenerated + public int hashCode() { + return Objects.hash(name); + } + + @Override + @SkipCoverageGenerated + public String toString() { + return name; + } +} diff --git a/server/src/main/java/io/whitefox/core/SharedTable.java b/server/src/main/java/io/whitefox/core/SharedTable.java index 594ec6a32..85a6abb65 100644 --- a/server/src/main/java/io/whitefox/core/SharedTable.java +++ b/server/src/main/java/io/whitefox/core/SharedTable.java @@ -5,25 +5,21 @@ public final class SharedTable { private final String name; - private final String location; private final String schema; private final String share; + private final InternalTable internalTable; - public SharedTable(String name, String location, String schema, String share) { + public SharedTable(String name, String schema, String share, InternalTable internalTable) { this.name = name; - this.location = location; this.schema = schema; this.share = share; + this.internalTable = internalTable; } public String name() { return name; } - public String location() { - return location; - } - public String schema() { return schema; } @@ -32,31 +28,35 @@ public String share() { return share; } + public InternalTable internalTable() { + return internalTable; + } + @Override @SkipCoverageGenerated - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (SharedTable) obj; - return Objects.equals(this.name, that.name) - && Objects.equals(this.location, that.location) - && Objects.equals(this.schema, that.schema) - && Objects.equals(this.share, that.share); + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SharedTable that = (SharedTable) o; + return Objects.equals(name, that.name) + && Objects.equals(schema, that.schema) + && Objects.equals(share, that.share) + && Objects.equals(internalTable, that.internalTable); } @Override @SkipCoverageGenerated public int hashCode() { - return Objects.hash(name, location, schema, share); + return Objects.hash(name, schema, share, internalTable); } @Override @SkipCoverageGenerated public String toString() { - return "Table[" + "name=" - + name + ", " + "location=" - + location + ", " + "schema=" - + schema + ", " + "share=" - + share + ']'; + return "SharedTable{" + "name='" + + name + '\'' + ", schema='" + + schema + '\'' + ", share='" + + share + '\'' + ", internalTable=" + + internalTable + '}'; } } diff --git a/server/src/main/java/io/whitefox/core/SharedTableName.java b/server/src/main/java/io/whitefox/core/SharedTableName.java new file mode 100644 index 000000000..ecc0cb7e0 --- /dev/null +++ b/server/src/main/java/io/whitefox/core/SharedTableName.java @@ -0,0 +1,37 @@ +package io.whitefox.core; + +import io.whitefox.annotations.SkipCoverageGenerated; +import java.util.Objects; + +public class SharedTableName { + private final String name; + + public SharedTableName(String name) { + this.name = name; + } + + public String name() { + return name; + } + + @Override + @SkipCoverageGenerated + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SharedTableName that = (SharedTableName) o; + return Objects.equals(name, that.name); + } + + @Override + @SkipCoverageGenerated + public int hashCode() { + return Objects.hash(name); + } + + @Override + @SkipCoverageGenerated + public String toString() { + return name; + } +} diff --git a/server/src/main/java/io/whitefox/core/services/DeltaSharedTable.java b/server/src/main/java/io/whitefox/core/services/DeltaSharedTable.java index 8617b0ac7..9c0158a54 100644 --- a/server/src/main/java/io/whitefox/core/services/DeltaSharedTable.java +++ b/server/src/main/java/io/whitefox/core/services/DeltaSharedTable.java @@ -17,24 +17,36 @@ public class DeltaSharedTable { private final DeltaLog deltaLog; private final TableSchemaConverter tableSchemaConverter; private final SharedTable tableDetails; + private final String location; private DeltaSharedTable( - DeltaLog deltaLog, TableSchemaConverter tableSchemaConverter, SharedTable sharedTable) { + DeltaLog deltaLog, + TableSchemaConverter tableSchemaConverter, + SharedTable sharedTable, + String location) { this.deltaLog = deltaLog; this.tableSchemaConverter = tableSchemaConverter; this.tableDetails = sharedTable; + this.location = location; } public static DeltaSharedTable of( SharedTable sharedTable, TableSchemaConverter tableSchemaConverter) { var configuration = new Configuration(); - var dataPath = sharedTable.location(); - var dt = DeltaLog.forTable(configuration, dataPath); - if (!dt.tableExists()) { + if (sharedTable.internalTable().properties() instanceof InternalTable.DeltaTableProperties) { + InternalTable.DeltaTableProperties deltaProps = + (InternalTable.DeltaTableProperties) sharedTable.internalTable().properties(); + var dataPath = deltaProps.location(); + var dt = DeltaLog.forTable(configuration, dataPath); + if (!dt.tableExists()) { + throw new IllegalArgumentException( + String.format("Cannot find a delta table at %s", dataPath)); + } + return new DeltaSharedTable(dt, tableSchemaConverter, sharedTable, dataPath); + } else { throw new IllegalArgumentException( - String.format("Cannot find a delta table at %s", dataPath)); + String.format("%s is not a delta table", sharedTable.name())); } - return new DeltaSharedTable(dt, tableSchemaConverter, sharedTable); } public static DeltaSharedTable of(SharedTable sharedTable) { @@ -114,7 +126,7 @@ private Optional getSnapshotForTimestampAsOf(long l) { private String location() { // remove all "/" at the end of the path - return tableDetails.location().replaceAll("/+$", ""); + return location.replaceAll("/+$", ""); } private Timestamp getTimestamp(String timestamp) { diff --git a/server/src/main/java/io/whitefox/core/services/ShareService.java b/server/src/main/java/io/whitefox/core/services/ShareService.java index c988f2ec2..a535dfbd4 100644 --- a/server/src/main/java/io/whitefox/core/services/ShareService.java +++ b/server/src/main/java/io/whitefox/core/services/ShareService.java @@ -1,12 +1,8 @@ package io.whitefox.core.services; -import io.whitefox.core.Principal; -import io.whitefox.core.Schema; -import io.whitefox.core.Share; +import io.whitefox.core.*; import io.whitefox.core.actions.CreateShare; -import io.whitefox.core.services.exceptions.SchemaAlreadyExists; -import io.whitefox.core.services.exceptions.ShareAlreadyExists; -import io.whitefox.core.services.exceptions.ShareNotFound; +import io.whitefox.core.services.exceptions.*; import io.whitefox.persistence.StorageManager; import jakarta.enterprise.context.ApplicationScoped; import java.time.Clock; @@ -21,10 +17,14 @@ public class ShareService { private final StorageManager storageManager; + + private final ProviderService providerService; + private final Clock clock; - public ShareService(StorageManager storageManager, Clock clock) { + public ShareService(StorageManager storageManager, ProviderService providerService, Clock clock) { this.storageManager = storageManager; + this.providerService = providerService; this.clock = clock; } @@ -74,7 +74,30 @@ public Share createSchema(String share, String schema, Principal requestPrincipa throw new SchemaAlreadyExists("Schema " + schema + " already exists in share " + share); } var newSchema = new Schema(schema, Collections.emptyList(), share); - var newShare = shareObj.addSchema(newSchema, requestPrincipal, clock.millis()); + var newShare = shareObj.upsertSchema(newSchema, requestPrincipal, clock.millis()); return storageManager.updateShare(newShare); } + + public Share addTableToSchema( + ShareName share, + SchemaName schema, + SharedTableName sharedTableName, + ProviderName providerName, + InternalTableName internalTableName, + Principal currentUser) { + var shareObj = storageManager + .getShare(share.name()) + .orElseThrow(() -> new ShareNotFound("Share " + share + "not found")); + var schemaObj = Optional.ofNullable(shareObj.schemas().get(schema.name())) + .orElseThrow(() -> new SchemaNotFound("Schema " + schema + " not found in share " + share)); + var providerObj = providerService + .getProvider(providerName.name()) + .orElseThrow(() -> new ProviderNotFound("Provider " + providerName + " not found")); + var tableObj = Optional.ofNullable(providerObj.tables().get(internalTableName.name())) + .orElseThrow(() -> new TableNotFound( + "Table " + internalTableName + " not found in provider " + providerName)); + // now that we know the request makes sense... + return storageManager.addTableToSchema( + shareObj, schemaObj, providerObj, tableObj, sharedTableName, currentUser, clock.millis()); + } } diff --git a/server/src/main/java/io/whitefox/core/services/exceptions/SchemaNotFound.java b/server/src/main/java/io/whitefox/core/services/exceptions/SchemaNotFound.java new file mode 100644 index 000000000..4c0e53878 --- /dev/null +++ b/server/src/main/java/io/whitefox/core/services/exceptions/SchemaNotFound.java @@ -0,0 +1,22 @@ +package io.whitefox.core.services.exceptions; + +public class SchemaNotFound extends NotFound { + public SchemaNotFound() {} + + public SchemaNotFound(String message) { + super(message); + } + + public SchemaNotFound(String message, Throwable cause) { + super(message, cause); + } + + public SchemaNotFound(Throwable cause) { + super(cause); + } + + public SchemaNotFound( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/server/src/main/java/io/whitefox/core/services/exceptions/TableNotFound.java b/server/src/main/java/io/whitefox/core/services/exceptions/TableNotFound.java new file mode 100644 index 000000000..ef9bdf813 --- /dev/null +++ b/server/src/main/java/io/whitefox/core/services/exceptions/TableNotFound.java @@ -0,0 +1,22 @@ +package io.whitefox.core.services.exceptions; + +public class TableNotFound extends NotFound { + public TableNotFound() {} + + public TableNotFound(String message) { + super(message); + } + + public TableNotFound(String message, Throwable cause) { + super(message, cause); + } + + public TableNotFound(Throwable cause) { + super(cause); + } + + public TableNotFound( + String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/server/src/main/java/io/whitefox/persistence/StorageManager.java b/server/src/main/java/io/whitefox/persistence/StorageManager.java index 8c137781a..7d4031b0a 100644 --- a/server/src/main/java/io/whitefox/persistence/StorageManager.java +++ b/server/src/main/java/io/whitefox/persistence/StorageManager.java @@ -38,4 +38,13 @@ Optional>> listTablesOfShare( Share createShare(Share share); Share updateShare(Share newShare); + + Share addTableToSchema( + Share shareObj, + Schema schemaObj, + Provider providerObj, + InternalTable table, + SharedTableName sharedTableName, + Principal currentUser, + long millis); } diff --git a/server/src/main/java/io/whitefox/persistence/memory/InMemoryStorageManager.java b/server/src/main/java/io/whitefox/persistence/memory/InMemoryStorageManager.java index 5dfbf09dc..fc4a7257e 100644 --- a/server/src/main/java/io/whitefox/persistence/memory/InMemoryStorageManager.java +++ b/server/src/main/java/io/whitefox/persistence/memory/InMemoryStorageManager.java @@ -241,4 +241,18 @@ public Share updateShare(Share newShare) { shares.put(newShare.name(), newShare); return shares.get(newShare.name()); } + + @Override + public Share addTableToSchema( + Share shareObj, + Schema schemaObj, + Provider providerObj, + InternalTable table, + SharedTableName sharedTableName, + Principal currentUser, + long millis) { + var newSchema = schemaObj.addTable(table, sharedTableName); + var newShare = shareObj.upsertSchema(newSchema, currentUser, millis); + return updateShare(newShare); + } } diff --git a/server/src/test/java/io/whitefox/MutableClock.java b/server/src/test/java/io/whitefox/MutableClock.java new file mode 100644 index 000000000..b581dcfb0 --- /dev/null +++ b/server/src/test/java/io/whitefox/MutableClock.java @@ -0,0 +1,42 @@ +package io.whitefox; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZoneOffset; + +public class MutableClock extends Clock { + + private Instant time; + + public MutableClock() { + this(Instant.ofEpochMilli(0)); + } + + public MutableClock(Instant instant) { + time = instant; + } + + @Override + public ZoneId getZone() { + return ZoneOffset.UTC; + } + + @Override + public Clock withZone(ZoneId zone) { + return new MutableClock(); + } + + @Override + public Instant instant() { + return time; + } + + public void tickMillis(long millis) { + time = time.plusMillis(millis); + } + + public void tickSeconds(long millis) { + time = time.plusSeconds(millis); + } +} diff --git a/server/src/test/java/io/whitefox/api/deltasharing/DeltaSharedTableTest.java b/server/src/test/java/io/whitefox/api/deltasharing/DeltaSharedTableTest.java index e17573346..de1cf2956 100644 --- a/server/src/test/java/io/whitefox/api/deltasharing/DeltaSharedTableTest.java +++ b/server/src/test/java/io/whitefox/api/deltasharing/DeltaSharedTableTest.java @@ -1,6 +1,6 @@ package io.whitefox.api.deltasharing; -import static io.whitefox.api.server.DeltaTestUtils.tablePath; +import static io.whitefox.api.server.DeltaTestUtils.deltaTable; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.wildfly.common.Assert.assertTrue; @@ -19,7 +19,7 @@ public class DeltaSharedTableTest { @Test void getTableVersion() throws ExecutionException, InterruptedException { - var PTable = new SharedTable("delta-table", tablePath("delta-table"), "default", "share1"); + var PTable = new SharedTable("delta-table", "default", "share1", deltaTable("delta-table")); var DTable = DeltaSharedTable.of(PTable); var version = DTable.getTableVersion(Optional.empty()); assertEquals(Optional.of(0L), version); @@ -27,7 +27,7 @@ void getTableVersion() throws ExecutionException, InterruptedException { @Test void getTableMetadata() { - var PTable = new SharedTable("delta-table", tablePath("delta-table"), "default", "share1"); + var PTable = new SharedTable("delta-table", "default", "share1", deltaTable("delta-table")); var DTable = DeltaSharedTable.of(PTable); var metadata = DTable.getMetadata(Optional.empty()); assertTrue(metadata.isPresent()); @@ -36,21 +36,21 @@ void getTableMetadata() { @Test void getUnknownTableMetadata() { - var unknownPTable = new SharedTable("notFound", "location1", "default", "share1"); + var unknownPTable = new SharedTable("notFound", "default", "share1", deltaTable("location1")); assertThrows(IllegalArgumentException.class, () -> DeltaSharedTable.of(unknownPTable)); } @Test void getTableVersionNonExistingTable() throws ExecutionException, InterruptedException { var PTable = - new SharedTable("delta-table", tablePath("delta-table-not-exists"), "default", "share1"); + new SharedTable("delta-table", "default", "share1", deltaTable("delta-table-not-exists")); var exception = assertThrows(IllegalArgumentException.class, () -> DeltaSharedTable.of(PTable)); assertTrue(exception.getMessage().startsWith("Cannot find a delta table at file")); } @Test void getTableVersionWithTimestamp() throws ExecutionException, InterruptedException { - var PTable = new SharedTable("delta-table", tablePath("delta-table"), "default", "share1"); + var PTable = new SharedTable("delta-table", "default", "share1", deltaTable("delta-table")); var DTable = DeltaSharedTable.of(PTable); var version = DTable.getTableVersion(Optional.of("2023-09-30T10:15:30+01:00")); assertEquals(Optional.empty(), version); @@ -58,7 +58,7 @@ void getTableVersionWithTimestamp() throws ExecutionException, InterruptedExcept @Test void getTableVersionWithFutureTimestamp() throws ExecutionException, InterruptedException { - var PTable = new SharedTable("delta-table", tablePath("delta-table"), "default", "share1"); + var PTable = new SharedTable("delta-table", "default", "share1", deltaTable("delta-table")); var DTable = DeltaSharedTable.of(PTable); var version = DTable.getTableVersion(Optional.of("2024-10-20T10:15:30+01:00")); assertEquals(Optional.empty(), version); @@ -66,7 +66,7 @@ void getTableVersionWithFutureTimestamp() throws ExecutionException, Interrupted @Test void getTableVersionWithMalformedTimestamp() throws ExecutionException, InterruptedException { - var PTable = new SharedTable("delta-table", tablePath("delta-table"), "default", "share1"); + var PTable = new SharedTable("delta-table", "default", "share1", deltaTable("delta-table")); var DTable = DeltaSharedTable.of(PTable); assertThrows( DateTimeParseException.class, diff --git a/server/src/test/java/io/whitefox/api/deltasharing/SampleTables.java b/server/src/test/java/io/whitefox/api/deltasharing/SampleTables.java index b397e1d6b..c805017be 100644 --- a/server/src/test/java/io/whitefox/api/deltasharing/SampleTables.java +++ b/server/src/test/java/io/whitefox/api/deltasharing/SampleTables.java @@ -1,24 +1,26 @@ package io.whitefox.api.deltasharing; +import static io.whitefox.api.server.DeltaTestUtils.deltaTable; +import static io.whitefox.api.server.DeltaTestUtils.deltaTableUri; + import io.whitefox.api.deltasharing.model.v1.generated.*; +import io.whitefox.core.InternalTable; import io.whitefox.core.Principal; import io.whitefox.core.SharedTable; import io.whitefox.persistence.StorageManager; import io.whitefox.persistence.memory.InMemoryStorageManager; -import java.nio.file.Paths; import java.util.List; import java.util.Map; import java.util.Set; public class SampleTables { - public static final String currentPath = - Paths.get(".").toAbsolutePath().normalize().toString(); - private static final Principal testPrincipal = new Principal("Mr. Fox"); - public static final String deltaTable1Path = - currentPath + "/src/test/resources/delta/samples/delta-table/"; - public static final String deltaTableWithHistory1Path = - currentPath + "/src/test/resources/delta/samples/delta-table-with-history/"; + public static final String deltaTable1Path = deltaTableUri("delta-table"); + + public static final InternalTable deltaTable1 = deltaTable("delta-table"); + public static final String deltaTableWithHistory1Path = deltaTableUri("delta-table-with-history"); + + public static final InternalTable deltaTableWithHistory1 = deltaTable("delta-table-with-history"); public static final StorageManager storageManager = new InMemoryStorageManager(List.of(new io.whitefox.core.Share( "name", @@ -28,12 +30,9 @@ public class SampleTables { new io.whitefox.core.Schema( "default", List.of( - new SharedTable("table1", "file://" + deltaTable1Path, "default", "name"), + new SharedTable("table1", "default", "name", deltaTable1), new SharedTable( - "table-with-history", - "file://" + deltaTableWithHistory1Path, - "default", - "name")), + "table-with-history", "default", "name", deltaTableWithHistory1)), "name")), testPrincipal, 0L))); @@ -63,9 +62,9 @@ public class SampleTables { public static final Set deltaTable1Files = Set.of( new FileObject() ._file(new FileObjectFile() - .url("file://" + deltaTable1Path + .url(deltaTable1Path + "part-00003-049d1c60-7ad6-45a3-af3f-65ffcabcc974-c000.snappy.parquet") - .id("file://" + deltaTable1Path + .id(deltaTable1Path + "part-00003-049d1c60-7ad6-45a3-af3f-65ffcabcc974-c000.snappy.parquet") .partitionValues(Map.of()) .size(478L) @@ -76,9 +75,9 @@ public class SampleTables { .expirationTimestamp(9223372036854775807L)), new FileObject() ._file(new FileObjectFile() - .url("file://" + deltaTable1Path + .url(deltaTable1Path + "part-00001-a67388a6-e20e-426e-a872-351c390779a5-c000.snappy.parquet") - .id("file://" + deltaTable1Path + .id(deltaTable1Path + "part-00001-a67388a6-e20e-426e-a872-351c390779a5-c000.snappy.parquet") .partitionValues(Map.of()) .size(478L) @@ -89,9 +88,9 @@ public class SampleTables { .expirationTimestamp(9223372036854775807L)), new FileObject() ._file(new FileObjectFile() - .url("file://" + deltaTable1Path + .url(deltaTable1Path + "part-00007-3e861bbf-fe8b-44d0-bac7-712b8cf4608c-c000.snappy.parquet") - .id("file://" + deltaTable1Path + .id(deltaTable1Path + "part-00007-3e861bbf-fe8b-44d0-bac7-712b8cf4608c-c000.snappy.parquet") .partitionValues(Map.of()) .size(478L) @@ -102,9 +101,9 @@ public class SampleTables { .expirationTimestamp(9223372036854775807L)), new FileObject() ._file(new FileObjectFile() - .url("file://" + deltaTable1Path + .url(deltaTable1Path + "part-00005-e7b9aad4-adf6-42ad-a17c-fbc93689b721-c000.snappy.parquet") - .id("file://" + deltaTable1Path + .id(deltaTable1Path + "part-00005-e7b9aad4-adf6-42ad-a17c-fbc93689b721-c000.snappy.parquet") .partitionValues(Map.of()) .size(478L) @@ -115,9 +114,9 @@ public class SampleTables { .expirationTimestamp(9223372036854775807L)), new FileObject() ._file(new FileObjectFile() - .url("file://" + deltaTable1Path + .url(deltaTable1Path + "part-00009-90280af8-7b24-4519-9e49-82db78a1651b-c000.snappy.parquet") - .id("file://" + deltaTable1Path + .id(deltaTable1Path + "part-00009-90280af8-7b24-4519-9e49-82db78a1651b-c000.snappy.parquet") .partitionValues(Map.of()) .size(478L) diff --git a/server/src/test/java/io/whitefox/api/deltasharing/loader/DeltaShareTableLoaderTest.java b/server/src/test/java/io/whitefox/api/deltasharing/loader/DeltaShareTableLoaderTest.java index ace793a2a..f9a30b342 100644 --- a/server/src/test/java/io/whitefox/api/deltasharing/loader/DeltaShareTableLoaderTest.java +++ b/server/src/test/java/io/whitefox/api/deltasharing/loader/DeltaShareTableLoaderTest.java @@ -1,6 +1,6 @@ package io.whitefox.api.deltasharing.loader; -import static io.whitefox.api.server.DeltaTestUtils.tablePath; +import static io.whitefox.api.server.DeltaTestUtils.deltaTable; import static org.junit.jupiter.api.Assertions.*; import io.quarkus.test.junit.QuarkusTest; @@ -25,7 +25,7 @@ public DeltaShareTableLoaderTest(DeltaShareTableLoader deltaShareTableLoader) { @DisabledOnOs(OS.WINDOWS) public void loadTable() { SharedTable sharedTable = - new SharedTable("delta-table", tablePath("delta-table"), "schema", "share"); + new SharedTable("delta-table", "schema", "share", deltaTable("delta-table")); DeltaSharedTable deltaSharedTable = deltaShareTableLoader.loadTable(sharedTable); assertTrue(deltaSharedTable.getTableVersion(Optional.empty()).isPresent()); assertEquals(0, deltaSharedTable.getTableVersion(Optional.empty()).get()); @@ -34,7 +34,7 @@ public void loadTable() { @Test public void loadUnknownTable() { SharedTable sharedTable = - new SharedTable("not-found", tablePath("not-found"), "schema", "share"); + new SharedTable("not-found", "schema", "share", deltaTable("not-found")); assertThrows( IllegalArgumentException.class, () -> deltaShareTableLoader.loadTable(sharedTable)); } diff --git a/server/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplTest.java b/server/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplTest.java index 43e8699ae..19f04d28b 100644 --- a/server/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplTest.java +++ b/server/src/test/java/io/whitefox/api/deltasharing/server/DeltaSharesApiImplTest.java @@ -153,7 +153,7 @@ public void listTables() { .then() .statusCode(200) .body("items", hasSize(2)) - .body("items[0].name", is("table1")) + .body("items[0].name", either(is("table1")).or(is("table-with-history"))) .body("items[0].schema", is("default")) .body("items[0].share", is("name")) .body("nextPageToken", is(nullValue())); @@ -216,7 +216,7 @@ public void listAllTables() { .then() .statusCode(200) .body("items", hasSize(2)) - .body("items[0].name", is("table1")) + .body("items[0].name", either(is("table1")).or(is("table-with-history"))) .body("items[0].schema", is("default")) .body("items[0].share", is("name")) .body("nextPageToken", is(nullValue())); diff --git a/server/src/test/java/io/whitefox/api/server/DeltaTestUtils.java b/server/src/test/java/io/whitefox/api/server/DeltaTestUtils.java index c62b3104d..c8774aec2 100644 --- a/server/src/test/java/io/whitefox/api/server/DeltaTestUtils.java +++ b/server/src/test/java/io/whitefox/api/server/DeltaTestUtils.java @@ -1,7 +1,10 @@ package io.whitefox.api.server; +import io.whitefox.core.InternalTable; +import io.whitefox.core.Principal; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; public class DeltaTestUtils { @@ -10,7 +13,26 @@ public class DeltaTestUtils { .resolve("src/test/resources/delta/samples") .toAbsolutePath(); - public static String tablePath(String tableName) { - return deltaTablesRoot.resolve(tableName).toUri().toString(); + public static String deltaTableUri(String tableName) { + return deltaTablesRoot + .resolve(tableName) + .toAbsolutePath() + .normalize() + .toUri() + .toString(); + } + + public static InternalTable deltaTable(String tableName) { + return new InternalTable( + tableName, + Optional.empty(), + new InternalTable.DeltaTableProperties(deltaTableUri(tableName)), + Optional.of(0L), + 0L, + new Principal("Mr. Fox"), + 0L, + new Principal("Mr. Fox"), + null // forgive me + ); } } diff --git a/server/src/test/java/io/whitefox/api/server/ShareV1ApiImplTest.java b/server/src/test/java/io/whitefox/api/server/ShareV1ApiImplTest.java index 86147b7bb..ef367a3c3 100644 --- a/server/src/test/java/io/whitefox/api/server/ShareV1ApiImplTest.java +++ b/server/src/test/java/io/whitefox/api/server/ShareV1ApiImplTest.java @@ -10,24 +10,32 @@ import io.restassured.http.Header; import io.restassured.internal.mapping.Jackson2Mapper; import io.restassured.response.ValidatableResponse; +import io.whitefox.MutableClock; import io.whitefox.OpenApiValidationFilter; import io.whitefox.api.model.v1.generated.AddRecipientToShareRequest; +import io.whitefox.api.model.v1.generated.AddTableToSchemaRequest; import io.whitefox.api.model.v1.generated.CreateShareInput; +import io.whitefox.api.model.v1.generated.TableReference; +import io.whitefox.core.Principal; +import io.whitefox.core.services.ProviderService; +import io.whitefox.core.services.ShareServiceTest; +import io.whitefox.core.services.StorageService; +import io.whitefox.core.services.TableService; import jakarta.inject.Inject; import java.nio.file.Paths; import java.time.Clock; -import java.time.Instant; -import java.time.ZoneId; import java.util.List; import org.junit.jupiter.api.*; @QuarkusTest @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class ShareV1ApiImplTest { + + private static final MutableClock clock = new MutableClock(); + @BeforeAll public static void setup() { - QuarkusMock.installMockForType( - Clock.fixed(Instant.ofEpochMilli(0L), ZoneId.of("UTC")), Clock.class); + QuarkusMock.installMockForType(clock, Clock.class); } private static final String wfSpecLocation = Paths.get(".") @@ -55,6 +63,15 @@ public static void setup() { @Inject private ObjectMapper objectMapper; + @Inject + private ProviderService providerService; + + @Inject + private StorageService storageService; + + @Inject + private TableService tableService; + @Test @Order(0) void createShare() { @@ -164,6 +181,42 @@ public void createSameSchema() { createSchemaInShare("share1", "schema1").statusCode(409); } + @Test + @Order(6) + public void addTableToSchema() { + clock.tickSeconds(10); + var share = "share1"; + var schema = "schema1"; + ShareServiceTest.setupInternalTable( + storageService, + providerService, + tableService, + new Principal("Mr. Fox"), + "storage1", + "provider1", + "table1"); + given() + .when() + .filter(wfFilter) + .body(new AddTableToSchemaRequest() + .name("shared1") + .reference(new TableReference().providerName("provider1").name("table1"))) + .contentType(ContentType.JSON) + .post("/whitefox-api/v1/shares/{share}/{schema}/tables", share, schema) + .then() + .statusCode(201) + .body("name", is("share1")) + .body("comment", is(nullValue())) + .body("recipients", is(hasSize(4))) + .body("schemas", is(hasSize(1))) + .body("schemas[0]", is("schema1")) + .body("createdAt", is(0)) + .body("createdBy", is("Mr. Fox")) + .body("updatedAt", is(10000)) + .body("updatedBy", is("Mr. Fox")) + .body("owner", is("Mr. Fox")); + } + ValidatableResponse createEmptyShare(String name) { return given() .when() @@ -187,7 +240,7 @@ ValidatableResponse createSchemaInShare(String share, String schema) { ValidatableResponse addRecipientsToShare(String share, List recipients) { return given() .when() - // .filter(wfFilter) // I need to disable the filter because it gets confused and + // .filter(wfFilter) // I need to disable the filter because it gets confused and // does not find the operation .body( new AddRecipientToShareRequest().principals(recipients), diff --git a/server/src/test/java/io/whitefox/core/services/DeltaLogServiceTest.java b/server/src/test/java/io/whitefox/core/services/DeltaLogServiceTest.java index 90419be7b..a976c8e20 100644 --- a/server/src/test/java/io/whitefox/core/services/DeltaLogServiceTest.java +++ b/server/src/test/java/io/whitefox/core/services/DeltaLogServiceTest.java @@ -1,6 +1,6 @@ package io.whitefox.core.services; -import static io.whitefox.api.server.DeltaTestUtils.tablePath; +import static io.whitefox.api.server.DeltaTestUtils.deltaTableUri; import static org.junit.jupiter.api.condition.OS.WINDOWS; import io.delta.standalone.DeltaLog; @@ -47,7 +47,7 @@ public class DeltaLogServiceTest { */ @Test void simpleTest() { - var log = DeltaLog.forTable(new Configuration(), tablePath("delta-table")); + var log = DeltaLog.forTable(new Configuration(), deltaTableUri("delta-table")); System.out.println("****"); System.out.println(log.snapshot().getAllFiles().get(0)); System.out.println("****"); diff --git a/server/src/test/java/io/whitefox/core/services/DeltaShareServiceTest.java b/server/src/test/java/io/whitefox/core/services/DeltaShareServiceTest.java index 81705d9a3..ec30ec501 100644 --- a/server/src/test/java/io/whitefox/core/services/DeltaShareServiceTest.java +++ b/server/src/test/java/io/whitefox/core/services/DeltaShareServiceTest.java @@ -1,6 +1,6 @@ package io.whitefox.core.services; -import static io.whitefox.api.server.DeltaTestUtils.tablePath; +import static io.whitefox.api.server.DeltaTestUtils.deltaTable; import static org.junit.jupiter.api.Assertions.*; import io.whitefox.core.Principal; @@ -101,7 +101,7 @@ public void listTables() throws ExecutionException, InterruptedException { "default", new Schema( "default", - List.of(new SharedTable("table1", "location1", "default", "name")), + List.of(new SharedTable("table1", "default", "name", deltaTable("location1"))), "name")))); StorageManager storageManager = new InMemoryStorageManager(shares); DeltaSharesService deltaSharesService = @@ -112,7 +112,7 @@ public void listTables() throws ExecutionException, InterruptedException { assertTrue(resultSchemas.get().getToken().isEmpty()); assertEquals(1, resultSchemas.get().getContent().size()); assertEquals( - new SharedTable("table1", "location1", "default", "name"), + new SharedTable("table1", "default", "name", deltaTable("location1")), resultSchemas.get().getContent().get(0)); } @@ -125,12 +125,12 @@ public void listAllTables() throws ExecutionException, InterruptedException { "default", new Schema( "default", - List.of(new SharedTable("table1", "location1", "default", "name")), + List.of(new SharedTable("table1", "default", "name", deltaTable("location1"))), "name"), "other", new Schema( "other", - List.of(new SharedTable("table2", "location2", "default", "name")), + List.of(new SharedTable("table2", "default", "name", deltaTable("location2"))), "name")))); StorageManager storageManager = new InMemoryStorageManager(shares); DeltaSharesService deltaSharesService = @@ -161,12 +161,12 @@ public void listAllTablesEmpty() throws ExecutionException, InterruptedException "default", new Schema( "default", - List.of(new SharedTable("table1", "location1", "default", "name")), + List.of(new SharedTable("table1", "default", "name", deltaTable("location1"))), "name"), "other", new Schema( "other", - List.of(new SharedTable("table2", "location2", "default", "name")), + List.of(new SharedTable("table2", "default", "name", deltaTable("location2"))), "name"))), createShare("name2", "key2", Map.of())); StorageManager storageManager = new InMemoryStorageManager(shares); @@ -199,7 +199,7 @@ public void getTableMetadata() { "default", new Schema( "default", - List.of(new SharedTable("table1", tablePath("delta-table"), "default", "name")), + List.of(new SharedTable("table1", "default", "name", deltaTable("delta-table"))), "name")))); StorageManager storageManager = new InMemoryStorageManager(shares); DeltaSharesService deltaSharesService = @@ -218,7 +218,7 @@ public void tableMetadataNotFound() { "default", new Schema( "default", - List.of(new SharedTable("table1", "location1", "default", "name")), + List.of(new SharedTable("table1", "default", "name", deltaTable("location1"))), "name")))); StorageManager storageManager = new InMemoryStorageManager(shares); DeltaSharesService deltaSharesService = diff --git a/server/src/test/java/io/whitefox/core/services/ShareServiceTest.java b/server/src/test/java/io/whitefox/core/services/ShareServiceTest.java index 4150a8e17..7bb9693b2 100644 --- a/server/src/test/java/io/whitefox/core/services/ShareServiceTest.java +++ b/server/src/test/java/io/whitefox/core/services/ShareServiceTest.java @@ -2,13 +2,12 @@ import static org.junit.jupiter.api.Assertions.*; -import io.whitefox.core.Principal; -import io.whitefox.core.Schema; -import io.whitefox.core.Share; +import io.whitefox.core.*; +import io.whitefox.core.actions.CreateInternalTable; +import io.whitefox.core.actions.CreateProvider; import io.whitefox.core.actions.CreateShare; -import io.whitefox.core.services.exceptions.SchemaAlreadyExists; -import io.whitefox.core.services.exceptions.ShareAlreadyExists; -import io.whitefox.core.services.exceptions.ShareNotFound; +import io.whitefox.core.actions.CreateStorage; +import io.whitefox.core.services.exceptions.*; import io.whitefox.persistence.StorageManager; import io.whitefox.persistence.memory.InMemoryStorageManager; import java.time.Clock; @@ -16,9 +15,10 @@ import java.time.ZoneOffset; import java.util.*; import java.util.concurrent.ExecutionException; +import java.util.function.Supplier; import org.junit.jupiter.api.Test; -class ShareServiceTest { +public class ShareServiceTest { Principal testPrincipal = new Principal("Mr. Fox"); Clock testClock = Clock.fixed(Instant.ofEpochMilli(7), ZoneOffset.UTC); @@ -26,7 +26,8 @@ class ShareServiceTest { @Test void createShare() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var providerService = newProviderService(storage, testClock); + var target = new ShareService(storage, providerService, testClock); var result = target.createShare(emptyCreateShare(), testPrincipal); assertEquals( new Share( @@ -46,7 +47,7 @@ void createShare() { @Test void failToCreateShare() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); assertThrows( ShareAlreadyExists.class, () -> target.createShare(emptyCreateShare(), testPrincipal)); @@ -55,7 +56,7 @@ void failToCreateShare() { @Test void addRecipientsToShare() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); target.addRecipientsToShare( "share1", List.of("Antonio", "Marco", "Aleksandar"), Principal::new, testPrincipal); @@ -78,7 +79,7 @@ void addRecipientsToShare() { @Test void addSameRecipientTwice() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); target.addRecipientsToShare( "share1", List.of("Antonio", "Marco", "Aleksandar"), Principal::new, testPrincipal); @@ -101,7 +102,7 @@ void addSameRecipientTwice() { @Test public void addRecipientToUnknownShare() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); assertThrows( ShareNotFound.class, () -> target.addRecipientsToShare( @@ -111,7 +112,7 @@ public void addRecipientToUnknownShare() { @Test public void getUnknownShare() throws ExecutionException, InterruptedException { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); assertEquals(Optional.empty(), target.getShare("unknown")); } @@ -119,7 +120,8 @@ public void getUnknownShare() throws ExecutionException, InterruptedException { public void getShare() throws ExecutionException, InterruptedException { var shares = List.of(createShare("name", "key", Collections.emptyMap())); StorageManager storageManager = new InMemoryStorageManager(shares); - var target = new ShareService(storageManager, testClock); + var target = + new ShareService(storageManager, newProviderService(storageManager, testClock), testClock); var share = target.getShare("name"); assertTrue(share.isPresent()); assertEquals("name", share.get().name()); @@ -129,7 +131,7 @@ public void getShare() throws ExecutionException, InterruptedException { @Test public void createSchema() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); var result = target.createSchema("share1", "schema1", testPrincipal); assertEquals( @@ -150,7 +152,7 @@ public void createSchema() { @Test public void failToCreateSameSchema() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); target.createSchema("share1", "schema1", testPrincipal); assertThrows( @@ -160,7 +162,7 @@ public void failToCreateSameSchema() { @Test public void createSecondSchema() { var storage = new InMemoryStorageManager(); - var target = new ShareService(storage, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); target.createShare(emptyCreateShare(), testPrincipal); target.createSchema("share1", "schema1", testPrincipal); var result = target.createSchema("share1", "schema2", testPrincipal); @@ -183,6 +185,195 @@ public void createSecondSchema() { result); } + @Test + public void addTableToSchema() { + var storage = new InMemoryStorageManager(); + var metastoreService = new MetastoreService(storage, testClock); + var storageService = new StorageService(storage, testClock); + var providerService = new ProviderService(storage, metastoreService, storageService, testClock); + var tableService = new TableService(storage, testClock, providerService); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); + var storageObj = storageService.createStorage(new CreateStorage( + "storage1", + Optional.empty(), + StorageType.S3, + testPrincipal, + "file://a/b/c", + false, + new StorageProperties.S3Properties(new AwsCredentials.SimpleAwsCredentials("", "", "")))); + var providerObj = providerService.createProvider( + new CreateProvider("provider1", storageObj.name(), Optional.empty(), testPrincipal)); + var tableObj = tableService.createInternalTable( + providerObj.name(), + testPrincipal, + new CreateInternalTable( + "table1", + Optional.empty(), + false, + new InternalTable.DeltaTableProperties("file://a/b/c/table"))); + target.createShare(emptyCreateShare(), testPrincipal); + target.createSchema("share1", "schema1", testPrincipal); + + target.addTableToSchema( + new ShareName("share1"), + new SchemaName("schema1"), + new SharedTableName("shared-1"), + new ProviderName(providerObj.name()), + new InternalTableName(tableObj.name()), + testPrincipal); + var sharedTables = target.getShare("share1").get().schemas().get("schema1").tables(); + assertEquals(1, sharedTables.size()); + var expected = new SharedTable("shared-1", "schema1", "share1", tableObj); + assertEquals(expected, sharedTables.stream().findFirst().get()); + var tablesFromDeltaService = new DeltaSharesServiceImpl( + storage, 100, new DeltaShareTableLoader(), new NoOpSigner()) + .listTablesOfShare("share1", Optional.empty(), Optional.empty()) + .get() + .getContent(); + assertEquals(1, tablesFromDeltaService.size()); + assertEquals(expected, tablesFromDeltaService.get(0)); + } + + @Test + public void failToAddTableToSchema() { + var storage = new InMemoryStorageManager(); + var metastoreService = new MetastoreService(storage, testClock); + var storageService = new StorageService(storage, testClock); + var providerService = new ProviderService(storage, metastoreService, storageService, testClock); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); + Supplier addTable = () -> target.addTableToSchema( + new ShareName("share1"), + new SchemaName("schema1"), + new SharedTableName("shared-1"), + new ProviderName("provider1"), + new InternalTableName("table1"), + testPrincipal); + assertThrows(ShareNotFound.class, addTable::get); + target.createShare(emptyCreateShare(), testPrincipal); + assertThrows(SchemaNotFound.class, addTable::get); + target.createSchema("share1", "schema1", testPrincipal); + assertThrows(ProviderNotFound.class, addTable::get); + var storageObj = storageService.createStorage(new CreateStorage( + "storage1", + Optional.empty(), + StorageType.S3, + testPrincipal, + "file://a/b/c", + false, + new StorageProperties.S3Properties(new AwsCredentials.SimpleAwsCredentials("", "", "")))); + providerService.createProvider( + new CreateProvider("provider1", storageObj.name(), Optional.empty(), testPrincipal)); + assertThrows(TableNotFound.class, addTable::get); + } + + @Test + public void addSameTableTwice() { + var storage = new InMemoryStorageManager(); + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); + target.createShare(emptyCreateShare(), testPrincipal); + target.createSchema("share1", "schema1", testPrincipal); + var result = target.createSchema("share1", "schema2", testPrincipal); + assertEquals( + new Share( + "share1", + "share1", + Map.of( + "schema1", + new Schema("schema1", Collections.emptyList(), "share1"), + "schema2", + new Schema("schema2", Collections.emptyList(), "share1")), + Optional.empty(), + Set.of(), + 7, + testPrincipal, + 7, + testPrincipal, + testPrincipal), + result); + } + + @Test + public void addSameTableTwiceToSchema() { + var storage = new InMemoryStorageManager(); + var metastoreService = new MetastoreService(storage, testClock); + var storageService = new StorageService(storage, testClock); + var providerService = new ProviderService(storage, metastoreService, storageService, testClock); + var tableService = new TableService(storage, testClock, providerService); + + var tableObj = setupInternalTable( + storageService, + providerService, + tableService, + testPrincipal, + "storage1", + "provider1", + "table1"); + var tableObj2 = tableService.createInternalTable( + tableObj.provider().name(), + testPrincipal, + new CreateInternalTable( + "table2", + Optional.empty(), + false, + new InternalTable.DeltaTableProperties("file://a/b/c/table"))); + + var target = new ShareService(storage, newProviderService(storage, testClock), testClock); + target.createShare(emptyCreateShare(), testPrincipal); + target.createSchema("share1", "schema1", testPrincipal); + + target.addTableToSchema( + new ShareName("share1"), + new SchemaName("schema1"), + new SharedTableName("shared-1"), + new ProviderName(tableObj.provider().name()), + new InternalTableName(tableObj.name()), + testPrincipal); + var sharedTables = target.getShare("share1").get().schemas().get("schema1").tables(); + assertEquals(1, sharedTables.size()); + var expected = new SharedTable("shared-1", "schema1", "share1", tableObj); + assertEquals(expected, sharedTables.stream().findFirst().get()); + target.addTableToSchema( + new ShareName("share1"), + new SchemaName("schema1"), + new SharedTableName("shared-1"), + new ProviderName(tableObj.provider().name()), + new InternalTableName(tableObj2.name()), + testPrincipal); + var sharedTables2 = target.getShare("share1").get().schemas().get("schema1").tables(); + assertEquals(1, sharedTables.size()); + var expected2 = new SharedTable("shared-1", "schema1", "share1", tableObj2); + assertEquals(expected2, sharedTables2.stream().findFirst().get()); + } + + public static InternalTable setupInternalTable( + StorageService storageService, + ProviderService providerService, + TableService tableService, + Principal testPrincipal, + String storageName, + String providerName, + String tableName) { + var storageObj = storageService.createStorage(new CreateStorage( + storageName, + Optional.empty(), + StorageType.S3, + testPrincipal, + "file://a/b/c", + false, + new StorageProperties.S3Properties(new AwsCredentials.SimpleAwsCredentials("", "", "")))); + var providerObj = providerService.createProvider( + new CreateProvider(providerName, storageObj.name(), Optional.empty(), testPrincipal)); + var tableObj = tableService.createInternalTable( + providerObj.name(), + testPrincipal, + new CreateInternalTable( + tableName, + Optional.empty(), + false, + new InternalTable.DeltaTableProperties("file://a/b/c/table"))); + return tableObj; + } + private Share createShare(String name, String key, Map schemas) { return new Share(name, key, schemas, testPrincipal, 0L); } @@ -191,4 +382,12 @@ private CreateShare emptyCreateShare() { return new CreateShare( "share1", Optional.empty(), Collections.emptyList(), Collections.emptyList()); } + + private ProviderService newProviderService(StorageManager storage, Clock testClock) { + return new ProviderService( + storage, + new MetastoreService(storage, testClock), + new StorageService(storage, testClock), + testClock); + } }