This is an automated email from the ASF dual-hosted git repository.

yuqi4733 pushed a commit to branch branch-lance-namespace-dev
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-lance-namespace-dev by 
this push:
     new fe2efa77fd [#8939] Improvement(lance-catalog): Make operating 
Gravitino store and Lance dataset atomic. (#9103)
fe2efa77fd is described below

commit fe2efa77fd65ee65fb82bc960a69755883d8e889
Author: Mini Yu <[email protected]>
AuthorDate: Thu Nov 13 17:11:46 2025 +0800

    [#8939] Improvement(lance-catalog): Make operating Gravitino store and 
Lance dataset atomic. (#9103)
    
    ### What changes were proposed in this pull request?
    
    If we failed to create the Lance dataset, we need to remove the data
    store in Gravitino store.
    
    ### Why are the changes needed?
    
    Once we failed to create a lance table, without this PR, we can't try
    again, as the metadata is already exists in the Graivitno server.
    
    Fix: #8939
    
    ### Does this PR introduce _any_ user-facing change?
    
    N/A
    
    ### How was this patch tested?
    
    ITs.
---
 .../GenericLakehouseCatalogOperations.java         | 78 +++++++++++++---------
 .../test/CatalogGenericLakehouseLanceIT.java       | 30 +++++++++
 .../lance/integration/test/LanceRESTServiceIT.java | 20 ++++++
 3 files changed, 97 insertions(+), 31 deletions(-)

diff --git 
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
 
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
index 5603ab1f22..6428fb816e 100644
--- 
a/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
+++ 
b/catalogs/catalog-generic-lakehouse/src/main/java/org/apache/gravitino/catalog/lakehouse/GenericLakehouseCatalogOperations.java
@@ -255,9 +255,8 @@ public class GenericLakehouseCatalogOperations
                 i -> ColumnEntity.toColumnEntity(columns[i], i, 
idGenerator.nextId(), auditInfo))
             .collect(Collectors.toList());
 
-    TableEntity entityToStore;
     try {
-      entityToStore =
+      TableEntity entityToStore =
           TableEntity.builder()
               .withName(ident.name())
               .withNamespace(ident.namespace())
@@ -273,41 +272,58 @@ public class GenericLakehouseCatalogOperations
               .withAuditInfo(auditInfo)
               .build();
       store.put(entityToStore);
+    } catch (EntityAlreadyExistsException e) {
+      throw new TableAlreadyExistsException(e, "Table %s already exists in the 
metadata", ident);
+    } catch (IOException e) {
+      throw new RuntimeException("Failed to create table metadata for " + 
ident, e);
+    }
 
-      // Get the value of register in table properties
-      boolean register =
-          Boolean.parseBoolean(
-              properties.getOrDefault(
-                  GenericLakehouseTablePropertiesMetadata.LAKEHOUSE_REGISTER, 
"false"));
-      if (register) {
-        // Do not need to create the physical table if this is a registration 
operation.
-        // Whether we need to check the existence of the physical table?
-        GenericLakehouseTable.Builder builder = 
GenericLakehouseTable.builder();
-        return builder
-            .withName(ident.name())
-            .withColumns(columns)
-            .withComment(comment)
-            .withProperties(properties)
-            .withDistribution(distribution)
-            .withIndexes(indexes)
-            .withAuditInfo(
-                AuditInfo.builder()
-                    .withCreator(PrincipalUtils.getCurrentUserName())
-                    .withCreateTime(Instant.now())
-                    .build())
-            .withPartitioning(partitions)
-            .withSortOrders(sortOrders)
-            .withFormat(LakehouseTableFormat.LANCE.lowerName())
-            .build();
-      }
+    // Get the value of register in table properties
+    boolean register =
+        Boolean.parseBoolean(
+            properties.getOrDefault(
+                GenericLakehouseTablePropertiesMetadata.LAKEHOUSE_REGISTER, 
"false"));
+    if (register) {
+      // Do not need to create the physical table if this is a registration 
operation.
+      // Whether we need to check the existence of the physical table?
+      GenericLakehouseTable.Builder builder = GenericLakehouseTable.builder();
+      return builder
+          .withName(ident.name())
+          .withColumns(columns)
+          .withComment(comment)
+          .withProperties(properties)
+          .withDistribution(distribution)
+          .withIndexes(indexes)
+          .withAuditInfo(
+              AuditInfo.builder()
+                  .withCreator(PrincipalUtils.getCurrentUserName())
+                  .withCreateTime(Instant.now())
+                  .build())
+          .withPartitioning(partitions)
+          .withSortOrders(sortOrders)
+          .withFormat(LakehouseTableFormat.LANCE.lowerName())
+          .build();
+    }
 
+    try {
       LakehouseCatalogOperations lanceCatalogOperations =
           getLakehouseCatalogOperations(newProperties);
       return lanceCatalogOperations.createTable(
           ident, columns, comment, newProperties, partitions, distribution, 
sortOrders, indexes);
-    } catch (EntityAlreadyExistsException e) {
-      throw new TableAlreadyExistsException(e, "Table %s already exists", 
ident);
-    } catch (IOException e) {
+    } catch (Exception e) {
+      // Try to roll back the metadata entry in Gravitino store
+      try {
+        store.delete(ident, Entity.EntityType.TABLE);
+      } catch (IOException ioException) {
+        LOG.error(
+            "Failed to roll back the metadata entry for table {} after 
physical table creation failure.",
+            ident,
+            ioException);
+      }
+      if (e.getClass().isAssignableFrom(RuntimeException.class)) {
+        throw e;
+      }
+
       throw new RuntimeException("Failed to create table " + ident, e);
     }
   }
diff --git 
a/catalogs/catalog-generic-lakehouse/src/test/java/org/apache/gravitino/catalog/lakehouse/integration/test/CatalogGenericLakehouseLanceIT.java
 
b/catalogs/catalog-generic-lakehouse/src/test/java/org/apache/gravitino/catalog/lakehouse/integration/test/CatalogGenericLakehouseLanceIT.java
index ca1e8ca003..0c92b43eef 100644
--- 
a/catalogs/catalog-generic-lakehouse/src/test/java/org/apache/gravitino/catalog/lakehouse/integration/test/CatalogGenericLakehouseLanceIT.java
+++ 
b/catalogs/catalog-generic-lakehouse/src/test/java/org/apache/gravitino/catalog/lakehouse/integration/test/CatalogGenericLakehouseLanceIT.java
@@ -53,6 +53,7 @@ import org.apache.gravitino.Catalog;
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.client.GravitinoMetalake;
+import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.integration.test.util.BaseIT;
 import org.apache.gravitino.integration.test.util.GravitinoITUtils;
 import org.apache.gravitino.rel.Column;
@@ -255,6 +256,35 @@ public class CatalogGenericLakehouseLanceIT extends BaseIT 
{
         
Arrays.asList(catalog.asTableCatalog().listTables(nameIdentifier.namespace()));
     Assertions.assertEquals(1, tableIdentifiers.size());
     Assertions.assertEquals(nameIdentifier, tableIdentifiers.get(0));
+
+    // Now try to simulate the location of lance table does not exist.
+    Map<String, String> newProperties = createProperties();
+    newProperties.put("format", "lance");
+    // Use a wrong location to let the table creation fail
+    newProperties.put("location", "hdfs://localhost:9000/wrong_location");
+
+    String nameNew = GravitinoITUtils.genRandomName(TABLE_PREFIX);
+    NameIdentifier newNameIdentifier = NameIdentifier.of(schemaName, nameNew);
+    Exception e =
+        Assertions.assertThrows(
+            Exception.class,
+            () -> {
+              catalog
+                  .asTableCatalog()
+                  .createTable(
+                      newNameIdentifier,
+                      columns,
+                      TABLE_COMMENT,
+                      newProperties,
+                      Transforms.EMPTY_TRANSFORM,
+                      null,
+                      null);
+            });
+
+    Assertions.assertTrue(e.getMessage().contains("Invalid user input"));
+
+    Assertions.assertThrows(
+        NoSuchTableException.class, () -> 
catalog.asTableCatalog().loadTable(newNameIdentifier));
   }
 
   @Test
diff --git 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
index c7454442b6..a63bb39307 100644
--- 
a/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
+++ 
b/lance/lance-rest-server/src/test/java/org/apache/gravitino/lance/integration/test/LanceRESTServiceIT.java
@@ -440,6 +440,26 @@ public class LanceRESTServiceIT extends BaseIT {
             });
     Assertions.assertTrue(exception.getMessage().contains("Table already 
exists"));
     Assertions.assertEquals(409, exception.getCode());
+
+    // Try to create a table with wrong location should fail
+    CreateEmptyTableRequest wrongLocationRequest = new 
CreateEmptyTableRequest();
+    wrongLocationRequest.setId(List.of(CATALOG_NAME, SCHEMA_NAME, 
"wrong_location_table"));
+    wrongLocationRequest.setLocation("hdfs://localhost:9000/invalid_path/");
+    LanceNamespaceException apiException =
+        Assertions.assertThrows(
+            LanceNamespaceException.class,
+            () -> {
+              ns.createEmptyTable(wrongLocationRequest);
+            });
+    Assertions.assertTrue(apiException.getMessage().contains("Invalid user 
input"));
+
+    // Correct the location and try again
+    String correctedLocation = tempDir + "/" + "wrong_location_table/";
+    wrongLocationRequest.setLocation(correctedLocation);
+    CreateEmptyTableResponse wrongLocationResponse =
+        Assertions.assertDoesNotThrow(() -> 
ns.createEmptyTable(wrongLocationRequest));
+    Assertions.assertNotNull(wrongLocationResponse);
+    Assertions.assertEquals(correctedLocation, 
wrongLocationResponse.getLocation());
   }
 
   @Test

Reply via email to