@@ -520,7 +520,8 @@ private void authorizeGrantOnTableLikeOperationOrThrow(
520520 PolarisResolvedPathWrapper tableLikeWrapper =
521521 resolutionManifest .getResolvedPath (
522522 identifier , PolarisEntityType .TABLE_LIKE , PolarisEntitySubType .ANY_SUBTYPE , true );
523- if (!subTypes .contains (tableLikeWrapper .getRawLeafEntity ().getSubType ())) {
523+ if (!resolutionManifest .getIsPassthroughFacade ()
524+ && !subTypes .contains (tableLikeWrapper .getRawLeafEntity ().getSubType ())) {
524525 CatalogHandler .throwNotFoundExceptionForTableLikeEntity (identifier , subTypes );
525526 }
526527
@@ -1679,14 +1680,29 @@ public boolean grantPrivilegeOnNamespaceToRole(
16791680 PolarisAuthorizableOperation .ADD_NAMESPACE_GRANT_TO_CATALOG_ROLE ;
16801681 authorizeGrantOnNamespaceOperationOrThrow (op , catalogName , namespace , catalogRoleName );
16811682
1683+ CatalogEntity catalogEntity =
1684+ findCatalogByName (catalogName )
1685+ .orElseThrow (() -> new NotFoundException ("Parent catalog %s not found" , catalogName ));
16821686 PolarisEntity catalogRoleEntity =
16831687 findCatalogRoleByName (catalogName , catalogRoleName )
16841688 .orElseThrow (() -> new NotFoundException ("CatalogRole %s not found" , catalogRoleName ));
16851689
16861690 PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest .getResolvedPath (namespace );
16871691 if (resolvedPathWrapper == null
16881692 || !resolvedPathWrapper .isFullyResolvedNamespace (catalogName , namespace )) {
1689- throw new NotFoundException ("Namespace %s not found" , namespace );
1693+ if (resolutionManifest .getIsPassthroughFacade ()) {
1694+ resolvedPathWrapper =
1695+ createSyntheticNamespaceEntities (catalogEntity , namespace , resolvedPathWrapper );
1696+ if (resolvedPathWrapper == null
1697+ || !resolvedPathWrapper .isFullyResolvedNamespace (catalogName , namespace )) {
1698+ throw new RuntimeException (
1699+ String .format (
1700+ "Failed to create synthetic namespace entities for namespace %s in catalog %s" ,
1701+ namespace .toString (), catalogName ));
1702+ }
1703+ } else {
1704+ throw new NotFoundException ("Namespace %s not found" , namespace );
1705+ }
16901706 }
16911707 List <PolarisEntity > catalogPath = resolvedPathWrapper .getRawParentPath ();
16921708 PolarisEntity namespaceEntity = resolvedPathWrapper .getRawLeafEntity ();
@@ -1730,6 +1746,79 @@ public boolean revokePrivilegeOnNamespaceFromRole(
17301746 .isSuccess ();
17311747 }
17321748
1749+ /**
1750+ * Creates and persists the missing synthetic namespace entities for external catalogs.
1751+ *
1752+ * @param catalogEntity the external passthrough facade catalog entity.
1753+ * @param namespace the expected fully resolved namespace to be created.
1754+ * @param existingPath the partially resolved path currently stored in the metastore.
1755+ * @return the fully resolved path wrapper.
1756+ */
1757+ private PolarisResolvedPathWrapper createSyntheticNamespaceEntities (
1758+ CatalogEntity catalogEntity , Namespace namespace , PolarisResolvedPathWrapper existingPath ) {
1759+
1760+ if (existingPath == null ) {
1761+ throw new IllegalStateException (
1762+ String .format ("Catalog entity %s does not exist." , catalogEntity .getName ()));
1763+ }
1764+
1765+ List <PolarisEntity > completePath = new ArrayList <>(existingPath .getRawFullPath ());
1766+ PolarisEntity currentParent = existingPath .getRawLeafEntity ();
1767+
1768+ String [] allNamespaceLevels = namespace .levels ();
1769+ int matchingLevel = -1 ;
1770+ for (PolarisEntity entity : completePath .subList (1 , completePath .size ())) {
1771+ if (entity .getName ().equals (allNamespaceLevels [matchingLevel + 1 ])) {
1772+ matchingLevel ++;
1773+ } else {
1774+ break ;
1775+ }
1776+ }
1777+
1778+ for (int i = matchingLevel + 1 ; i < allNamespaceLevels .length ; i ++) {
1779+ String namespacePart = allNamespaceLevels [i ];
1780+
1781+ // TODO: Instead of creating synthetic entitties, rely on external catalog mediated backfill.
1782+ PolarisEntity syntheticNamespace =
1783+ new PolarisEntity .Builder ()
1784+ .setId (metaStoreManager .generateNewEntityId (getCurrentPolarisContext ()).getId ())
1785+ .setCatalogId (catalogEntity .getId ())
1786+ .setParentId (currentParent .getId ())
1787+ .setType (PolarisEntityType .NAMESPACE )
1788+ .setName (namespacePart )
1789+ .setCreateTimestamp (System .currentTimeMillis ())
1790+ .build ();
1791+
1792+ EntityResult result =
1793+ metaStoreManager .createEntityIfNotExists (
1794+ getCurrentPolarisContext (),
1795+ PolarisEntity .toCoreList (completePath ),
1796+ syntheticNamespace );
1797+
1798+ if (result .isSuccess ()) {
1799+ syntheticNamespace = PolarisEntity .of (result .getEntity ());
1800+ } else {
1801+ Namespace partialNamespace = Namespace .of (Arrays .copyOf (allNamespaceLevels , i + 1 ));
1802+ PolarisResolvedPathWrapper partialPath =
1803+ resolutionManifest .getResolvedPath (partialNamespace );
1804+ PolarisEntity partialLeafEntity = partialPath .getRawLeafEntity ();
1805+ if (partialLeafEntity == null
1806+ || !(partialLeafEntity .getName ().equals (namespacePart )
1807+ && partialLeafEntity .getType () == PolarisEntityType .NAMESPACE )) {
1808+ throw new RuntimeException (
1809+ String .format (
1810+ "Failed to create or find namespace entity '%s' in federated catalog '%s'" ,
1811+ namespacePart , catalogEntity .getName ()));
1812+ }
1813+ syntheticNamespace = partialLeafEntity ;
1814+ }
1815+ completePath .add (syntheticNamespace );
1816+ currentParent = syntheticNamespace ;
1817+ }
1818+ PolarisResolvedPathWrapper resolvedPathWrapper = resolutionManifest .getResolvedPath (namespace );
1819+ return resolvedPathWrapper ;
1820+ }
1821+
17331822 public boolean grantPrivilegeOnTableToRole (
17341823 String catalogName ,
17351824 String catalogRoleName ,
@@ -2007,9 +2096,9 @@ private boolean grantPrivilegeOnTableLikeToRole(
20072096 TableIdentifier identifier ,
20082097 List <PolarisEntitySubType > subTypes ,
20092098 PolarisPrivilege privilege ) {
2010- if ( findCatalogByName ( catalogName ). isEmpty ()) {
2011- throw new NotFoundException ( "Parent catalog %s not found" , catalogName );
2012- }
2099+ CatalogEntity catalogEntity =
2100+ findCatalogByName ( catalogName )
2101+ . orElseThrow (() -> new NotFoundException ( "Parent catalog %s not found" , catalogName ));
20132102 PolarisEntity catalogRoleEntity =
20142103 findCatalogRoleByName (catalogName , catalogRoleName )
20152104 .orElseThrow (() -> new NotFoundException ("CatalogRole %s not found" , catalogRoleName ));
@@ -2019,7 +2108,20 @@ private boolean grantPrivilegeOnTableLikeToRole(
20192108 identifier , PolarisEntityType .TABLE_LIKE , PolarisEntitySubType .ANY_SUBTYPE );
20202109 if (resolvedPathWrapper == null
20212110 || !subTypes .contains (resolvedPathWrapper .getRawLeafEntity ().getSubType ())) {
2022- CatalogHandler .throwNotFoundExceptionForTableLikeEntity (identifier , subTypes );
2111+ if (resolutionManifest .getIsPassthroughFacade ()) {
2112+ resolvedPathWrapper =
2113+ createSyntheticTableLikeEntities (
2114+ catalogEntity , identifier , subTypes , resolvedPathWrapper );
2115+ if (resolvedPathWrapper == null
2116+ || !subTypes .contains (resolvedPathWrapper .getRawLeafEntity ().getSubType ())) {
2117+ throw new RuntimeException (
2118+ String .format (
2119+ "Failed to create synthetic table-like entity for table %s in catalog %s" ,
2120+ identifier .name (), catalogEntity .getName ()));
2121+ }
2122+ } else {
2123+ CatalogHandler .throwNotFoundExceptionForTableLikeEntity (identifier , subTypes );
2124+ }
20232125 }
20242126 List <PolarisEntity > catalogPath = resolvedPathWrapper .getRawParentPath ();
20252127 PolarisEntity tableLikeEntity = resolvedPathWrapper .getRawLeafEntity ();
@@ -2034,6 +2136,74 @@ private boolean grantPrivilegeOnTableLikeToRole(
20342136 .isSuccess ();
20352137 }
20362138
2139+ /**
2140+ * Creates and persists the missing synthetic table-like entity and its parent namespace entities
2141+ * for external catalogs.
2142+ *
2143+ * @param catalogEntity the external passthrough facade catalog entity.
2144+ * @param identifier the path of the table-like entity(including the namespace).
2145+ * @param subTypes the expected subtypes of the table-like entity
2146+ * @param existingPathWrapper the partially resolved path currently stored in the metastore.
2147+ * @return the resolved path wrapper
2148+ */
2149+ private PolarisResolvedPathWrapper createSyntheticTableLikeEntities (
2150+ CatalogEntity catalogEntity ,
2151+ TableIdentifier identifier ,
2152+ List <PolarisEntitySubType > subTypes ,
2153+ PolarisResolvedPathWrapper existingPathWrapper ) {
2154+
2155+ Namespace namespace = identifier .namespace ();
2156+ PolarisResolvedPathWrapper resolvedNamespacePathWrapper =
2157+ !namespace .isEmpty ()
2158+ ? createSyntheticNamespaceEntities (catalogEntity , namespace , existingPathWrapper )
2159+ : existingPathWrapper ;
2160+
2161+ if (resolvedNamespacePathWrapper == null
2162+ || (!namespace .isEmpty ()
2163+ && !resolvedNamespacePathWrapper .isFullyResolvedNamespace (
2164+ catalogEntity .getName (), namespace ))) {
2165+ throw new RuntimeException (
2166+ String .format (
2167+ "Failed to create synthetic namespace entities for namespace %s in catalog %s" ,
2168+ namespace .toString (), catalogEntity .getName ()));
2169+ }
2170+
2171+ // TODO: Instead of creating a synthetic table-like entity, rely on external catalog mediated
2172+ // backfill.
2173+ PolarisEntity parentNamespaceEntity = resolvedNamespacePathWrapper .getRawLeafEntity ();
2174+ PolarisEntity syntheticTableEntity =
2175+ new PolarisEntity .Builder ()
2176+ .setId (metaStoreManager .generateNewEntityId (getCurrentPolarisContext ()).getId ())
2177+ .setCatalogId (parentNamespaceEntity .getCatalogId ())
2178+ .setParentId (parentNamespaceEntity .getId ())
2179+ .setType (PolarisEntityType .TABLE_LIKE )
2180+ .setSubType (subTypes .get (0 ))
2181+ .setName (identifier .name ())
2182+ .setCreateTimestamp (System .currentTimeMillis ())
2183+ .build ();
2184+
2185+ EntityResult result =
2186+ metaStoreManager .createEntityIfNotExists (
2187+ getCurrentPolarisContext (),
2188+ PolarisEntity .toCoreList (resolvedNamespacePathWrapper .getRawFullPath ()),
2189+ syntheticTableEntity );
2190+
2191+ if (result .isSuccess ()) {
2192+ syntheticTableEntity = PolarisEntity .of (result .getEntity ());
2193+ } else {
2194+ PolarisResolvedPathWrapper tablePathWrapper = resolutionManifest .getResolvedPath (identifier );
2195+ PolarisEntity leafEntity =
2196+ tablePathWrapper != null ? tablePathWrapper .getRawLeafEntity () : null ;
2197+ if (leafEntity == null || !subTypes .contains (leafEntity .getSubType ())) {
2198+ throw new RuntimeException (
2199+ String .format (
2200+ "Failed to create or find table entity '%s' in federated catalog '%s'" ,
2201+ identifier .name (), catalogEntity .getName ()));
2202+ }
2203+ }
2204+ return resolutionManifest .getResolvedPath (identifier );
2205+ }
2206+
20372207 /**
20382208 * Removes a table-level or view-level grant on {@code identifier} from {@code catalogRoleName}.
20392209 */
0 commit comments