dennishuo commented on code in PR #2223:
URL: https://github.com/apache/polaris/pull/2223#discussion_r2253065317


##########
service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -1679,14 +1680,29 @@ public boolean grantPrivilegeOnNamespaceToRole(
         PolarisAuthorizableOperation.ADD_NAMESPACE_GRANT_TO_CATALOG_ROLE;
     authorizeGrantOnNamespaceOperationOrThrow(op, catalogName, namespace, 
catalogRoleName);
 
+    CatalogEntity catalogEntity =
+        findCatalogByName(catalogName)
+            .orElseThrow(() -> new NotFoundException("Parent catalog %s not 
found", catalogName));
     PolarisEntity catalogRoleEntity =
         findCatalogRoleByName(catalogName, catalogRoleName)
             .orElseThrow(() -> new NotFoundException("CatalogRole %s not 
found", catalogRoleName));
 
     PolarisResolvedPathWrapper resolvedPathWrapper = 
resolutionManifest.getResolvedPath(namespace);
     if (resolvedPathWrapper == null
         || !resolvedPathWrapper.isFullyResolvedNamespace(catalogName, 
namespace)) {
-      throw new NotFoundException("Namespace %s not found", namespace);
+      if (resolutionManifest.getIsPassthroughFacade()) {

Review Comment:
   We may also want to add a 
[FeatureConfiguration](https://github.com/apache/polaris/blob/main/polaris-core/src/main/java/org/apache/polaris/core/config/FeatureConfiguration.java)
 to gate this path; even if federation is still "experimental" so we can change 
default behaviors, this persistence-backfill is definitely something people 
might still potentially decide to disable.
   
   I'd make the feature configuration be a service-level config (no 
`catalogConfig`) because the gate really is more of something the service owner 
would want to be able to prevent catalog-users from using than a self opt-in 
per-catalog.
   
   Maybe something like `ENABLE_SUB_CATALOG_RBAC_FOR_FEDERATED_CATALOGS`, 
though maybe you can come up with a better config name :)



##########
service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -2034,6 +2136,74 @@ private boolean grantPrivilegeOnTableLikeToRole(
         .isSuccess();
   }
 
+  /**
+   * Creates and persists the missing synthetic table-like entity and its 
parent namespace entities
+   * for external catalogs.
+   *
+   * @param catalogEntity the external passthrough facade catalog entity.
+   * @param identifier the path of the table-like entity(including the 
namespace).
+   * @param subTypes the expected subtypes of the table-like entity
+   * @param existingPathWrapper the partially resolved path currently stored 
in the metastore.
+   * @return the resolved path wrapper
+   */
+  private PolarisResolvedPathWrapper createSyntheticTableLikeEntities(
+      CatalogEntity catalogEntity,
+      TableIdentifier identifier,
+      List<PolarisEntitySubType> subTypes,
+      PolarisResolvedPathWrapper existingPathWrapper) {
+
+    Namespace namespace = identifier.namespace();
+    PolarisResolvedPathWrapper resolvedNamespacePathWrapper =
+        !namespace.isEmpty()
+            ? createSyntheticNamespaceEntities(catalogEntity, namespace, 
existingPathWrapper)
+            : existingPathWrapper;
+
+    if (resolvedNamespacePathWrapper == null
+        || (!namespace.isEmpty()
+            && !resolvedNamespacePathWrapper.isFullyResolvedNamespace(
+                catalogEntity.getName(), namespace))) {
+      throw new RuntimeException(
+          String.format(
+              "Failed to create synthetic namespace entities for namespace %s 
in catalog %s",
+              namespace.toString(), catalogEntity.getName()));
+    }
+
+    // TODO: Instead of creating a synthetic table-like entity, rely on 
external catalog mediated
+    // backfill.
+    PolarisEntity parentNamespaceEntity = 
resolvedNamespacePathWrapper.getRawLeafEntity();
+    PolarisEntity syntheticTableEntity =
+        new PolarisEntity.Builder()
+            
.setId(metaStoreManager.generateNewEntityId(getCurrentPolarisContext()).getId())
+            .setCatalogId(parentNamespaceEntity.getCatalogId())
+            .setParentId(parentNamespaceEntity.getId())
+            .setType(PolarisEntityType.TABLE_LIKE)
+            .setSubType(subTypes.get(0))

Review Comment:
   Unfortunately, we probably don't want to do this. Looks like the current 
behavior will be to create a `GENERIC_TABLE` for the synthetic entity, which 
will cause problems once we want to start leaning more on the JIT-created 
entities for other things like actually having the metadata location, etc.
   
   It might need to be ugly at first -- it's even better to just special-case 
ICEBERG_TABLE vs VIEW (and ignore GENERIC_TABLE at first) for now. Maybe 
extract a helper to hide the ugliness that takes `subTypes` and basically  does:
   
       if (subTypes.contains(ICEBERG_TABLE)) {
         return ICEBERG_TABLE;
       } else if (subTypes.contains(VIEW) {
         return VIEW;
       } else {
         throw new IllegalStateException(...)
        }
   
   For now that is "correct" because our federation catalog factory (last I 
checked) only supports Iceberg remote catalogs, and nothing for GENERIC_TABLE.
   
   Once we also support GENERIC_TABLE federation, we'd need the intended type 
at the callsite anyways to even know whether the "remote catalog for mediating 
the backfill" should be instantiated via an Iceberg RESTCatalog factory or a 
different factory for GenericCatalogs.
   
   That might be a big hurdle because of ICEBERG_TABLE and GENERIC_TABLE living 
in the same "type" namespace, but that will be a separate workstream anyways.
   
   We can document this issue in another `// TODO:` comment.



##########
service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -1730,6 +1746,79 @@ public boolean revokePrivilegeOnNamespaceFromRole(
         .isSuccess();
   }
 
+  /**
+   * Creates and persists the missing synthetic namespace entities for 
external catalogs.
+   *
+   * @param catalogEntity the external passthrough facade catalog entity.
+   * @param namespace the expected fully resolved namespace to be created.
+   * @param existingPath the partially resolved path currently stored in the 
metastore.
+   * @return the fully resolved path wrapper.
+   */
+  private PolarisResolvedPathWrapper createSyntheticNamespaceEntities(
+      CatalogEntity catalogEntity, Namespace namespace, 
PolarisResolvedPathWrapper existingPath) {
+
+    if (existingPath == null) {
+      throw new IllegalStateException(
+          String.format("Catalog entity %s does not exist.", 
catalogEntity.getName()));
+    }
+
+    List<PolarisEntity> completePath = new 
ArrayList<>(existingPath.getRawFullPath());
+    PolarisEntity currentParent = existingPath.getRawLeafEntity();
+
+    String[] allNamespaceLevels = namespace.levels();
+    int matchingLevel = -1;
+    for (PolarisEntity entity : completePath.subList(1, completePath.size())) {
+      if (entity.getName().equals(allNamespaceLevels[matchingLevel + 1])) {
+        matchingLevel++;
+      } else {
+        break;
+      }
+    }
+
+    for (int i = matchingLevel + 1; i < allNamespaceLevels.length; i++) {
+      String namespacePart = allNamespaceLevels[i];
+
+      // TODO: Instead of creating synthetic entitties, rely on external 
catalog mediated backfill.
+      PolarisEntity syntheticNamespace =
+          new PolarisEntity.Builder()

Review Comment:
   We should try to use the 
[NamespaceEntity.Builder](https://github.com/apache/polaris/blob/d6ae2c89de2178113665ffa73ab773b0108da4fe/polaris-core/src/main/java/org/apache/polaris/core/entity/NamespaceEntity.java#L69)
 if possible.
   
   Also, it's necessary to call 
[setParentNamespace](https://github.com/apache/polaris/blob/d6ae2c89de2178113665ffa73ab773b0108da4fe/polaris-core/src/main/java/org/apache/polaris/core/entity/NamespaceEntity.java#L82C20-L82C38)
 because `listGrants*` relies on it.



##########
service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -1730,6 +1746,79 @@ public boolean revokePrivilegeOnNamespaceFromRole(
         .isSuccess();
   }
 
+  /**
+   * Creates and persists the missing synthetic namespace entities for 
external catalogs.
+   *
+   * @param catalogEntity the external passthrough facade catalog entity.
+   * @param namespace the expected fully resolved namespace to be created.
+   * @param existingPath the partially resolved path currently stored in the 
metastore.
+   * @return the fully resolved path wrapper.
+   */
+  private PolarisResolvedPathWrapper createSyntheticNamespaceEntities(
+      CatalogEntity catalogEntity, Namespace namespace, 
PolarisResolvedPathWrapper existingPath) {
+
+    if (existingPath == null) {
+      throw new IllegalStateException(
+          String.format("Catalog entity %s does not exist.", 
catalogEntity.getName()));
+    }
+
+    List<PolarisEntity> completePath = new 
ArrayList<>(existingPath.getRawFullPath());
+    PolarisEntity currentParent = existingPath.getRawLeafEntity();
+
+    String[] allNamespaceLevels = namespace.levels();
+    int matchingLevel = -1;
+    for (PolarisEntity entity : completePath.subList(1, completePath.size())) {

Review Comment:
   Some code comments would be nice here to explain why the `subList` starts at 
1 (is it because 0 is the CatalogEntity?)
   
   Also this all might be more readable if we shift-right by one, instead of 
needing `matchingLevel + 1` in all the places that use it. Basically make it a 
*count* instead of the level:
   
       int numMatchingLevels = 0;
       ....
       ... (allNamespaceLevels[numMatchingLevels])...
       ...
       for (int i = numMatchingLevels; ...
       ...
       ...Arrays.copyOf(allNamespaceLevels, i)...
   
   Though the semantics of "matchingLevel" does make sense in the normal case, 
relying on `matchingLevel == -1` for `i = matchingLevel + 1` in the case where 
there are 0 matches is a bit jarring when reading the code.



##########
service/common/src/main/java/org/apache/polaris/service/admin/PolarisAdminService.java:
##########
@@ -2034,6 +2136,74 @@ private boolean grantPrivilegeOnTableLikeToRole(
         .isSuccess();
   }
 
+  /**
+   * Creates and persists the missing synthetic table-like entity and its 
parent namespace entities
+   * for external catalogs.
+   *
+   * @param catalogEntity the external passthrough facade catalog entity.
+   * @param identifier the path of the table-like entity(including the 
namespace).
+   * @param subTypes the expected subtypes of the table-like entity
+   * @param existingPathWrapper the partially resolved path currently stored 
in the metastore.
+   * @return the resolved path wrapper
+   */
+  private PolarisResolvedPathWrapper createSyntheticTableLikeEntities(
+      CatalogEntity catalogEntity,
+      TableIdentifier identifier,
+      List<PolarisEntitySubType> subTypes,
+      PolarisResolvedPathWrapper existingPathWrapper) {
+
+    Namespace namespace = identifier.namespace();
+    PolarisResolvedPathWrapper resolvedNamespacePathWrapper =
+        !namespace.isEmpty()
+            ? createSyntheticNamespaceEntities(catalogEntity, namespace, 
existingPathWrapper)
+            : existingPathWrapper;
+
+    if (resolvedNamespacePathWrapper == null
+        || (!namespace.isEmpty()
+            && !resolvedNamespacePathWrapper.isFullyResolvedNamespace(
+                catalogEntity.getName(), namespace))) {
+      throw new RuntimeException(
+          String.format(
+              "Failed to create synthetic namespace entities for namespace %s 
in catalog %s",
+              namespace.toString(), catalogEntity.getName()));
+    }
+
+    // TODO: Instead of creating a synthetic table-like entity, rely on 
external catalog mediated
+    // backfill.
+    PolarisEntity parentNamespaceEntity = 
resolvedNamespacePathWrapper.getRawLeafEntity();
+    PolarisEntity syntheticTableEntity =
+        new PolarisEntity.Builder()

Review Comment:
   We should try to use the 
[IcebergTableLikeEntity.Builder](https://github.com/apache/polaris/blob/d6ae2c89de2178113665ffa73ab773b0108da4fe/polaris-core/src/main/java/org/apache/polaris/core/entity/table/IcebergTableLikeEntity.java#L76)
 here instead of the general builder. I understand it might be a bit messy due 
to the `metadataLocation` argument in the builder constructor, but as far as I 
can tell it's graceful enough to accept `""` for the metadataLocation, and 
actually this is kind of working as intended for the Builder to help enforce 
the existence of expected fields in the entity type.
   
   Once we have the remote-mediated backfill, we'll get to actually plug in the 
real `metadataLocation` to the constructor which will benefit everyone.
   
   Also, it's important to 
[setTableIdentifier](https://github.com/apache/polaris/blob/d6ae2c89de2178113665ffa73ab773b0108da4fe/polaris-core/src/main/java/org/apache/polaris/core/entity/table/IcebergTableLikeEntity.java#L93C20-L93C38)
 because `listGrants*` depends on it (otherwise, normally a leaf entity doesn't 
have a representation of its parent)



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscr...@polaris.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org

Reply via email to