This is an automated email from the ASF dual-hosted git repository.
russellspitzer pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/polaris.git
The following commit(s) were added to refs/heads/main by this push:
new 1c8ca73 Add NotificationType.VALIDATE which can serve as a dry-run of
a CREATE without a metadata file (#321)
1c8ca73 is described below
commit 1c8ca73e9d2e736c4ad4fb6e639cef04db54ea68
Author: Dennis Huo <[email protected]>
AuthorDate: Fri Sep 27 15:08:24 2024 -0700
Add NotificationType.VALIDATE which can serve as a dry-run of a CREATE
without a metadata file (#321)
If a remote catalog or manual caller wants to ensure that permissions,
paths, etc., are configured
correctly to receive CREATE/UPDATE notifications before deciding to
actually create a table in the
remote catalog, sending a VALIDATE notification with the prospective table
metadata path can
pre-validate basic setup. In a VALIDATE call, no actual entities will be
mutated or created.
---
.../service/catalog/BasePolarisCatalog.java | 52 +++++++-
.../polaris/service/types/NotificationType.java | 3 +-
.../service/catalog/BasePolarisCatalogTest.java | 136 +++++++++++++++++++++
.../PolarisCatalogHandlerWrapperAuthzTest.java | 121 ++++++++++++------
.../catalog/PolarisRestCatalogIntegrationTest.java | 24 +++-
spec/rest-catalog-open-api.yaml | 12 +-
6 files changed, 306 insertions(+), 42 deletions(-)
diff --git
a/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
b/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
index 5e46d60..ff5dfef 100644
---
a/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
+++
b/polaris-service/src/main/java/org/apache/polaris/service/catalog/BasePolarisCatalog.java
@@ -171,7 +171,7 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
private CloseableGroup closeableGroup;
private Map<String, String> catalogProperties;
private Map<String, String> tableDefaultProperties;
- private final FileIOFactory fileIOFactory;
+ private FileIOFactory fileIOFactory;
private PolarisMetaStoreManager metaStoreManager;
/**
@@ -1613,6 +1613,11 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
return metaStoreManager;
}
+ @VisibleForTesting
+ void setFileIOFactory(FileIOFactory newFactory) {
+ this.fileIOFactory = newFactory;
+ }
+
@VisibleForTesting
long getCatalogId() {
// TODO: Properly handle initialization
@@ -1873,6 +1878,51 @@ public class BasePolarisCatalog extends
BaseMetastoreViewCatalog
if (notificationType == NotificationType.DROP) {
return dropTableLike(PolarisEntitySubType.TABLE, tableIdentifier,
Map.of(), false /* purge */)
.isSuccess();
+ } else if (notificationType == NotificationType.VALIDATE) {
+ // In this mode we don't want to make any mutations, so we won't
auto-create non-existing
+ // parent namespaces. This means when we want to validate
allowedLocations for the proposed
+ // table metadata location, we must independently find the deepest
non-null parent namespace
+ // of the TableIdentifier, which may even be the base CatalogEntity if
no parent namespaces
+ // actually exist yet. We can then extract the right StorageInfo entity
via a normal call
+ // to findStorageInfoFromHierarchy.
+ PolarisResolvedPathWrapper resolvedStorageEntity = null;
+ Optional<PolarisEntity> storageInfoEntity = Optional.empty();
+ for (int i = tableIdentifier.namespace().length(); i >= 0; i--) {
+ Namespace nsLevel =
+ Namespace.of(
+ Arrays.stream(tableIdentifier.namespace().levels())
+ .limit(i)
+ .toArray(String[]::new));
+ resolvedStorageEntity = resolvedEntityView.getResolvedPath(nsLevel);
+ if (resolvedStorageEntity != null) {
+ storageInfoEntity =
findStorageInfoFromHierarchy(resolvedStorageEntity);
+ break;
+ }
+ }
+
+ if (resolvedStorageEntity == null || storageInfoEntity.isEmpty()) {
+ throw new BadRequestException(
+ "Failed to find StorageInfo entity for TableIdentifier %s",
tableIdentifier);
+ }
+
+ // Validate location against the resolvedStorageEntity
+ String metadataLocation =
+
transformTableLikeLocation(request.getPayload().getMetadataLocation());
+ validateLocationForTableLike(tableIdentifier, metadataLocation,
resolvedStorageEntity);
+
+ // Validate that we can construct a FileIO
+ String locationDir = metadataLocation.substring(0,
metadataLocation.lastIndexOf("/"));
+ refreshIOWithCredentials(
+ tableIdentifier,
+ Set.of(locationDir),
+ resolvedStorageEntity,
+ new HashMap<>(tableDefaultProperties),
+ Set.of(PolarisStorageActions.READ));
+
+ LOGGER.debug(
+ "Successful VALIDATE notification for tableIdentifier {},
metadataLocation {}",
+ tableIdentifier,
+ metadataLocation);
} else if (notificationType == NotificationType.CREATE
|| notificationType == NotificationType.UPDATE) {
diff --git
a/polaris-service/src/main/java/org/apache/polaris/service/types/NotificationType.java
b/polaris-service/src/main/java/org/apache/polaris/service/types/NotificationType.java
index 3189a59..53d7d47 100644
---
a/polaris-service/src/main/java/org/apache/polaris/service/types/NotificationType.java
+++
b/polaris-service/src/main/java/org/apache/polaris/service/types/NotificationType.java
@@ -30,7 +30,8 @@ public enum NotificationType {
UNKNOWN(0, "UNKNOWN"),
CREATE(1, "CREATE"),
UPDATE(2, "UPDATE"),
- DROP(3, "DROP");
+ DROP(3, "DROP"),
+ VALIDATE(4, "VALIDATE");
NotificationType(int id, String displayName) {
this.id = id;
diff --git
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java
index 63f859b..1888bde 100644
---
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java
+++
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/BasePolarisCatalogTest.java
@@ -360,6 +360,142 @@ public class BasePolarisCatalogTest extends
CatalogTests<BasePolarisCatalog> {
.hasMessageContaining("Parent");
}
+ @Test
+ public void testValidateNotificationWhenTableAndNamespacesDontExist() {
+ Assumptions.assumeTrue(
+ requiresNamespaceCreate(),
+ "Only applicable if namespaces must be created before adding
children");
+ Assumptions.assumeTrue(
+ supportsNestedNamespaces(), "Only applicable if nested namespaces are
supported");
+ Assumptions.assumeTrue(
+ supportsNotifications(), "Only applicable if notifications are
supported");
+
+ final String tableLocation =
"s3://externally-owned-bucket/validate_table/";
+ final String tableMetadataLocation = tableLocation +
"metadata/v1.metadata.json";
+ BasePolarisCatalog catalog = catalog();
+
+ Namespace namespace = Namespace.of("parent", "child1");
+ TableIdentifier table = TableIdentifier.of(namespace, "table");
+
+ // For a VALIDATE request we can pass in a full metadata JSON filename or
just the table's
+ // metadata directory; either way the path will be validated to be under
the allowed locations,
+ // but any actual metadata JSON file will not be accessed.
+ NotificationRequest request = new NotificationRequest();
+ request.setNotificationType(NotificationType.VALIDATE);
+ TableUpdateNotification update = new TableUpdateNotification();
+ update.setMetadataLocation(tableMetadataLocation);
+ update.setTableName(table.name());
+ update.setTableUuid(UUID.randomUUID().toString());
+ update.setTimestamp(230950845L);
+ request.setPayload(update);
+
+ // We should be able to send the notification without creating the
metadata file since it's
+ // only validating the ability to send the CREATE/UPDATE notification
possibly before actually
+ // creating the table at all on the remote catalog.
+ Assertions.assertThat(catalog.sendNotification(table, request))
+ .as("Notification should be sent successfully")
+ .isTrue();
+ Assertions.assertThat(catalog.namespaceExists(namespace))
+ .as("Intermediate namespaces should not be created")
+ .isFalse();
+ Assertions.assertThat(catalog.tableExists(table))
+ .as("Table should not be created for a VALIDATE notification")
+ .isFalse();
+
+ // Now also check that despite creating the metadata file, the validation
call still doesn't
+ // create any namespaces or tables.
+ InMemoryFileIO fileIO = (InMemoryFileIO) catalog.getIo();
+ fileIO.addFile(
+ tableMetadataLocation,
+
TableMetadataParser.toJson(createSampleTableMetadata(tableLocation)).getBytes(UTF_8));
+
+ Assertions.assertThat(catalog.sendNotification(table, request))
+ .as("Notification should be sent successfully")
+ .isTrue();
+ Assertions.assertThat(catalog.namespaceExists(namespace))
+ .as("Intermediate namespaces should not be created")
+ .isFalse();
+ Assertions.assertThat(catalog.tableExists(table))
+ .as("Table should not be created for a VALIDATE notification")
+ .isFalse();
+ }
+
+ @Test
+ public void testValidateNotificationInDisallowedLocation() {
+ Assumptions.assumeTrue(
+ requiresNamespaceCreate(),
+ "Only applicable if namespaces must be created before adding
children");
+ Assumptions.assumeTrue(
+ supportsNestedNamespaces(), "Only applicable if nested namespaces are
supported");
+ Assumptions.assumeTrue(
+ supportsNotifications(), "Only applicable if notifications are
supported");
+
+ // The location of the metadata JSON file specified in the create will be
forbidden.
+ // For a VALIDATE call we can pass in the metadata/ prefix itself instead
of a metadata JSON
+ // filename.
+ final String tableLocation = "s3://forbidden-table-location/table/";
+ final String tableMetadataLocation = tableLocation + "metadata/";
+ BasePolarisCatalog catalog = catalog();
+
+ Namespace namespace = Namespace.of("parent", "child1");
+ TableIdentifier table = TableIdentifier.of(namespace, "table");
+
+ NotificationRequest request = new NotificationRequest();
+ request.setNotificationType(NotificationType.VALIDATE);
+ TableUpdateNotification update = new TableUpdateNotification();
+ update.setMetadataLocation(tableMetadataLocation);
+ update.setTableName(table.name());
+ update.setTableUuid(UUID.randomUUID().toString());
+ update.setTimestamp(230950845L);
+ request.setPayload(update);
+
+ Assertions.assertThatThrownBy(() -> catalog.sendNotification(table,
request))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Invalid location");
+ }
+
+ @Test
+ public void testValidateNotificationFailToCreateFileIO() {
+ Assumptions.assumeTrue(
+ requiresNamespaceCreate(),
+ "Only applicable if namespaces must be created before adding
children");
+ Assumptions.assumeTrue(
+ supportsNestedNamespaces(), "Only applicable if nested namespaces are
supported");
+ Assumptions.assumeTrue(
+ supportsNotifications(), "Only applicable if notifications are
supported");
+
+ // The location of the metadata JSON file specified in the create will be
allowed, but
+ // we'll inject a separate ForbiddenException during FileIO instantiation.
+ // For a VALIDATE call we can pass in the metadata/ prefix itself instead
of a metadata JSON
+ // filename.
+ final String tableLocation =
"s3://externally-owned-bucket/validate_table/";
+ final String tableMetadataLocation = tableLocation + "metadata/";
+ BasePolarisCatalog catalog = catalog();
+
+ Namespace namespace = Namespace.of("parent", "child1");
+ TableIdentifier table = TableIdentifier.of(namespace, "table");
+
+ NotificationRequest request = new NotificationRequest();
+ request.setNotificationType(NotificationType.VALIDATE);
+ TableUpdateNotification update = new TableUpdateNotification();
+ update.setMetadataLocation(tableMetadataLocation);
+ update.setTableName(table.name());
+ update.setTableUuid(UUID.randomUUID().toString());
+ update.setTimestamp(230950845L);
+ request.setPayload(update);
+
+ catalog.setFileIOFactory(
+ new FileIOFactory() {
+ @Override
+ public FileIO loadFileIO(String impl, Map<String, String>
properties) {
+ throw new ForbiddenException("Fake failure applying downscoped
credentials");
+ }
+ });
+ Assertions.assertThatThrownBy(() -> catalog.sendNotification(table,
request))
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessageContaining("Fake failure applying downscoped credentials");
+ }
+
@Test
public void testUpdateNotificationWhenTableAndNamespacesDontExist() {
Assumptions.assumeTrue(
diff --git
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
index 537f585..0f6fc41 100644
---
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
+++
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisCatalogHandlerWrapperAuthzTest.java
@@ -1636,33 +1636,43 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
String tableUuid = UUID.randomUUID().toString();
- NotificationRequest request = new NotificationRequest();
- request.setNotificationType(NotificationType.CREATE);
- TableUpdateNotification update = new TableUpdateNotification();
- update.setMetadataLocation(
+ NotificationRequest createRequest = new NotificationRequest();
+ createRequest.setNotificationType(NotificationType.CREATE);
+ TableUpdateNotification createPayload = new TableUpdateNotification();
+ createPayload.setMetadataLocation(
String.format("%s/bucket/table/metadata/v1.metadata.json",
storageLocation));
- update.setTableName(table.name());
- update.setTableUuid(tableUuid);
- update.setTimestamp(230950845L);
- request.setPayload(update);
-
- NotificationRequest request2 = new NotificationRequest();
- request2.setNotificationType(NotificationType.UPDATE);
- TableUpdateNotification update2 = new TableUpdateNotification();
- update2.setMetadataLocation(
+ createPayload.setTableName(table.name());
+ createPayload.setTableUuid(tableUuid);
+ createPayload.setTimestamp(230950845L);
+ createRequest.setPayload(createPayload);
+
+ NotificationRequest updateRequest = new NotificationRequest();
+ updateRequest.setNotificationType(NotificationType.UPDATE);
+ TableUpdateNotification updatePayload = new TableUpdateNotification();
+ updatePayload.setMetadataLocation(
String.format("%s/bucket/table/metadata/v2.metadata.json",
storageLocation));
- update2.setTableName(table.name());
- update2.setTableUuid(tableUuid);
- update2.setTimestamp(330950845L);
- request2.setPayload(update2);
-
- NotificationRequest request3 = new NotificationRequest();
- request3.setNotificationType(NotificationType.DROP);
- TableUpdateNotification update3 = new TableUpdateNotification();
- update3.setTableName(table.name());
- update3.setTableUuid(tableUuid);
- update3.setTimestamp(430950845L);
- request3.setPayload(update3);
+ updatePayload.setTableName(table.name());
+ updatePayload.setTableUuid(tableUuid);
+ updatePayload.setTimestamp(330950845L);
+ updateRequest.setPayload(updatePayload);
+
+ NotificationRequest dropRequest = new NotificationRequest();
+ dropRequest.setNotificationType(NotificationType.DROP);
+ TableUpdateNotification dropPayload = new TableUpdateNotification();
+ dropPayload.setTableName(table.name());
+ dropPayload.setTableUuid(tableUuid);
+ dropPayload.setTimestamp(430950845L);
+ dropRequest.setPayload(dropPayload);
+
+ NotificationRequest validateRequest = new NotificationRequest();
+ validateRequest.setNotificationType(NotificationType.VALIDATE);
+ TableUpdateNotification validatePayload = new TableUpdateNotification();
+ validatePayload.setMetadataLocation(
+ String.format("%s/bucket/table/metadata/v1.metadata.json",
storageLocation));
+ validatePayload.setTableName(table.name());
+ validatePayload.setTableUuid(tableUuid);
+ validatePayload.setTimestamp(530950845L);
+ validateRequest.setPayload(validatePayload);
PolarisCallContextCatalogFactory factory =
new PolarisCallContextCatalogFactory(
@@ -1697,13 +1707,14 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
.assignUUID()
.build();
TableMetadataParser.overwrite(
- tableMetadata,
fileIO.newOutputFile(update.getMetadataLocation()));
+ tableMetadata,
fileIO.newOutputFile(createPayload.getMetadataLocation()));
TableMetadataParser.overwrite(
- tableMetadata,
fileIO.newOutputFile(update2.getMetadataLocation()));
+ tableMetadata,
fileIO.newOutputFile(updatePayload.getMetadataLocation()));
return catalog;
}
};
- doTestSufficientPrivilegeSets(
+
+ List<Set<PolarisPrivilege>> sufficientPrivilegeSets =
List.of(
Set.of(PolarisPrivilege.CATALOG_MANAGE_CONTENT),
Set.of(PolarisPrivilege.TABLE_FULL_METADATA,
PolarisPrivilege.NAMESPACE_FULL_METADATA),
@@ -1721,14 +1732,18 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
PolarisPrivilege.TABLE_DROP,
PolarisPrivilege.TABLE_WRITE_PROPERTIES,
PolarisPrivilege.NAMESPACE_CREATE,
- PolarisPrivilege.NAMESPACE_DROP)),
+ PolarisPrivilege.NAMESPACE_DROP));
+ doTestSufficientPrivilegeSets(
+ sufficientPrivilegeSets,
() -> {
newWrapper(Set.of(PRINCIPAL_ROLE1), externalCatalog, factory)
- .sendNotification(table, request);
+ .sendNotification(table, createRequest);
newWrapper(Set.of(PRINCIPAL_ROLE1), externalCatalog, factory)
- .sendNotification(table, request2);
+ .sendNotification(table, updateRequest);
newWrapper(Set.of(PRINCIPAL_ROLE1), externalCatalog, factory)
- .sendNotification(table, request3);
+ .sendNotification(table, dropRequest);
+ newWrapper(Set.of(PRINCIPAL_ROLE1), externalCatalog, factory)
+ .sendNotification(table, validateRequest);
},
() -> {
newWrapper(Set.of(PRINCIPAL_ROLE2), externalCatalog, factory)
@@ -1738,6 +1753,17 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
},
PRINCIPAL_NAME,
externalCatalog);
+
+ // Also test VALIDATE in isolation
+ doTestSufficientPrivilegeSets(
+ sufficientPrivilegeSets,
+ () -> {
+ newWrapper(Set.of(PRINCIPAL_ROLE1), externalCatalog, factory)
+ .sendNotification(table, validateRequest);
+ },
+ null /* cleanupAction */,
+ PRINCIPAL_NAME,
+ externalCatalog);
}
@Test
@@ -1746,7 +1772,6 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
TableIdentifier table = TableIdentifier.of(namespace, "tbl1");
NotificationRequest request = new NotificationRequest();
- request.setNotificationType(NotificationType.UPDATE);
TableUpdateNotification update = new TableUpdateNotification();
update.setMetadataLocation("file:///tmp/bucket/table/metadata/v1.metadata.json");
update.setTableName(table.name());
@@ -1754,11 +1779,37 @@ public class PolarisCatalogHandlerWrapperAuthzTest
extends PolarisAuthzTestBase
update.setTimestamp(230950845L);
request.setPayload(update);
- doTestInsufficientPrivileges(
+ List<PolarisPrivilege> insufficientPrivileges =
List.of(
PolarisPrivilege.NAMESPACE_FULL_METADATA,
PolarisPrivilege.TABLE_FULL_METADATA,
- PolarisPrivilege.VIEW_FULL_METADATA),
+ PolarisPrivilege.VIEW_FULL_METADATA);
+
+ // Independently test insufficient privileges in isolation.
+ request.setNotificationType(NotificationType.CREATE);
+ doTestInsufficientPrivileges(
+ insufficientPrivileges,
+ () -> {
+ newWrapper(Set.of(PRINCIPAL_ROLE1)).sendNotification(table, request);
+ });
+
+ request.setNotificationType(NotificationType.UPDATE);
+ doTestInsufficientPrivileges(
+ insufficientPrivileges,
+ () -> {
+ newWrapper(Set.of(PRINCIPAL_ROLE1)).sendNotification(table, request);
+ });
+
+ request.setNotificationType(NotificationType.DROP);
+ doTestInsufficientPrivileges(
+ insufficientPrivileges,
+ () -> {
+ newWrapper(Set.of(PRINCIPAL_ROLE1)).sendNotification(table, request);
+ });
+
+ request.setNotificationType(NotificationType.VALIDATE);
+ doTestInsufficientPrivileges(
+ insufficientPrivileges,
() -> {
newWrapper(Set.of(PRINCIPAL_ROLE1)).sendNotification(table, request);
});
diff --git
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisRestCatalogIntegrationTest.java
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisRestCatalogIntegrationTest.java
index 7b937fd..a7062bd 100644
---
a/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisRestCatalogIntegrationTest.java
+++
b/polaris-service/src/test/java/org/apache/polaris/service/catalog/PolarisRestCatalogIntegrationTest.java
@@ -759,12 +759,28 @@ public class PolarisRestCatalogIntegrationTest extends
CatalogTests<RESTCatalog>
"s3://my-bucket/path/to/metadata.json",
null));
restCatalog.createNamespace(Namespace.of("ns1"));
+ String notificationUrl =
+ String.format(
+
"http://localhost:%d/api/catalog/v1/%s/namespaces/ns1/tables/tbl1/notifications",
+ EXT.getLocalPort(), currentCatalogName);
try (Response response =
EXT.client()
- .target(
- String.format(
-
"http://localhost:%d/api/catalog/v1/%s/namespaces/ns1/tables/tbl1/notifications",
- EXT.getLocalPort(), currentCatalogName))
+ .target(notificationUrl)
+ .request("application/json")
+ .header("Authorization", "Bearer " + userToken)
+ .header(REALM_PROPERTY_KEY, realm)
+ .post(Entity.json(notification))) {
+ assertThat(response)
+ .returns(Response.Status.BAD_REQUEST.getStatusCode(),
Response::getStatus)
+ .extracting(r -> r.readEntity(ErrorResponse.class))
+ .returns("Cannot update internal catalog via notifications",
ErrorResponse::message);
+ }
+
+ // NotificationType.VALIDATE should also surface the same error.
+ notification.setNotificationType(NotificationType.VALIDATE);
+ try (Response response =
+ EXT.client()
+ .target(notificationUrl)
.request("application/json")
.header("Authorization", "Bearer " + userToken)
.header(REALM_PROPERTY_KEY, realm)
diff --git a/spec/rest-catalog-open-api.yaml b/spec/rest-catalog-open-api.yaml
index 6bea02a..7c22d5b 100644
--- a/spec/rest-catalog-open-api.yaml
+++ b/spec/rest-catalog-open-api.yaml
@@ -984,6 +984,15 @@ paths:
The responsibility of ensuring the correct order of timestamps for a
sequence of notifications
lies with the caller of the API. This includes managing potential
clock skew or inconsistencies
when notifications are sent from multiple sources.
+
+ A VALIDATE request behaves like a dry-run of a CREATE or UPDATE
request up to but not including
+ loading the contents of a metadata file; this includes validations
of permissions, the specified
+ metadata path being within ALLOWED_LOCATIONS, having an EXTERNAL
catalog, etc. The intended use
+ case for a VALIDATE notification is to allow a remote catalog to
pre-validate the general
+ settings of a receiving catalog against an intended new table
location before possibly creating
+ a table intended for sending notifcations in the remote catalog at
all. For a VALIDATE request,
+ the specified metadata-location can either be a prospective full
metadata file path, or a
+ relevant parent directory of the intended table to validate against
ALLOWED_LOCATIONS.
content:
application/json:
schema:
@@ -3244,6 +3253,7 @@ components:
- CREATE
- UPDATE
- DROP
+ - VALIDATE
TableUpdateNotification:
type: object
@@ -4181,4 +4191,4 @@ components:
catalog: Allows interacting with the Config and Catalog APIs
BearerAuth:
type: http
- scheme: bearer
\ No newline at end of file
+ scheme: bearer