jerryshao commented on code in PR #13771:
URL: https://github.com/apache/gravitino/pull/13771#discussion_r4255232645


##########
core/src/main/java/org/apache/gravitino/catalog/TopicOperationDispatcher.java:
##########
@@ -309,6 +309,10 @@ private void importTopic(NameIdentifier identifier) {
             .build();
 
     try {
+      // Kafka does not persist a removable Gravitino identifier. Its 
immutable topic UUID is
+      // converted to the entity ID on every load, so catalog re-registration 
must rebind that ID
+      // to the current Gravitino path. Overwrite is safe for this 
Kafka-specific identity model:
+      // Kafka does not support topic rename, and deleting a topic gives a 
replacement a new UUID.

Review Comment:
   [Important] Both claims in this comment are true, but neither covers the 
case this PR exists to fix: one Kafka cluster behind two Gravitino paths.
   
   `KafkaCatalogOperations` derives the entity ID from the Kafka topic UUID 
alone (`convertToGravitinoId`, `KafkaCatalogOperations.java:567`, used at 
`:237` and `:284`); nothing scopes it to a catalog or metalake, and nothing 
stops two Kafka catalogs - in one metalake or in two - from pointing at the 
same `bootstrap.servers`. Loading the topic through the second path reaches 
`insertTopicMetaOnDuplicateKeyUpdate` (`TopicMetaService.java:84`), whose `ON 
DUPLICATE KEY UPDATE` rewrites `topic_name`, `metalake_id`, `catalog_id` and 
`schema_id` on the existing row 
(`provider/base/TopicMetaBaseSQLProvider.java:74-79`). The topic entity, and 
the TOPIC privileges keyed on its ID (`AuthorizationUtils.java:116-118`, 
`MetadataObjectUtil.java:266`), move to whichever path was loaded last. That is 
the #13770 failure mode, for topics, and it is reachable without any rename or 
recreate.
   
   The re-registration argument on @mchades' thread is sound - the ID is not 
removable, so create-only would wedge Kafka catalogs. But the fix can be 
narrower than an unconditional overwrite: rebind only when the existing row is 
soft-deleted (the state re-registration actually produces) and refuse when it 
is live under another path. If that is more than this PR should carry, the 
comment and the "user-facing change" section should name the multi-path 
exposure as a known limitation rather than call overwrite safe.
   
   Verified by: read `TopicOperationDispatcher.java:267-320` and 
`TopicMetaService.insertTopic:64-95` on this branch; read the upsert SQL in 
`provider/base/TopicMetaBaseSQLProvider.java:55-84`; `grep -n 
convertToGravitinoId` in `KafkaCatalogOperations.java` shows no catalog or 
metalake component; `JDBCBackend.isSoftDeleted:1037-1049` has no TOPIC case, so 
topics also get no soft-deleted diagnosis.



##########
spark-connector/spark-common/src/test/java/org/apache/gravitino/spark/connector/integration/test/SparkCommonIT.java:
##########
@@ -630,7 +630,7 @@ protected void testAlterTableReplaceColumns() {
 
     tableInfo = getTableInfo(tableName);
     checkTableColumns(tableName, updateColumns, tableInfo);
-    sql(String.format("INSERT INTO %S VALUES(3, 'name2', 10)", tableName));
+    sql(String.format("INSERT INTO %s VALUES(3, 'name2', 10)", tableName));

Review Comment:
   [Important] Lowercasing this statement removes the only coverage of 
mixed-case identifier resolution, while the behaviour it exposed is still 
unfixed.
   
   `%S` has been in this shared base test since before this PR and passes on 
`main`. It fails on this branch for the Hive-backed Iceberg suite because 
`IcebergCatalogCapability` has no `caseSensitiveOnName` override, so 
`TEST_REPLACE_COLUMNS_TABLE` reaches the dispatcher un-normalized; Iceberg's 
HiveCatalog resolves it to the lowercase Hive table along with its 
`gravitino.identifier`; the create-only write at 
`TableOperationDispatcher.java:598` then conflicts with the live row, the 
reload at `:185` still finds nothing under the uppercase path, and `loadTable` 
throws the manual-cleanup error (`:188-196`). On `main` the same load silently 
renamed the stored row, which is the bug this PR fixes - so the refusal is 
intended, but it also means ordinary mixed-case SQL against a case-insensitive 
backend now fails at load.
   
   386e00b fixed that with the capability override; 03272ce reverted the 
override (correctly, per the two open threads on it) and this line is what took 
its place. With it, nothing in the suite exercises the uppercase path, so the 
regression is invisible rather than resolved. Either normalize identifiers for 
backends that fold case and keep `%S` here, or keep `%S` and record the new 
behaviour in a per-suite override so it is visible and attributable.
   
   Verified by: `git show origin/main:...SparkCommonIT.java` shows `%S` at this 
line on main; `git diff --name-only origin/main...HEAD` no longer lists 
`IcebergCatalogCapability.java`; `grep -rn caseSensitiveOnName api/ catalogs/ 
core/` finds implementations only for Hive and Glue; read 
`TableOperationDispatcher.java:166-197` and `544-607` on this branch.



##########
catalogs/catalog-hive/src/test/java/org/apache/gravitino/catalog/hive/integration/test/CatalogHive2IT.java:
##########
@@ -1458,20 +1460,81 @@ public void testOutOfBandRenameKeepsColumnTags() throws 
InterruptedException {
             return null;
           });
 
-      // The column keeps its id, so the tag follows the table to its new name.
+      // Loading the external name now requires manual identifier cleanup. The 
rejected import must
+      // leave the tag relation attached to the original Gravitino identity.
       NameIdentifier newIdent = NameIdentifier.of(schema, newName);
-      Column column = loadColumn(newIdent, HIVE_COL_NAME1);
-      Assertions.assertArrayEquals(new String[] {tagName}, 
column.supportsTags().listTags());
+      UnsupportedOperationException exception =
+          assertThrows(
+              UnsupportedOperationException.class, () -> loadColumn(newIdent, 
HIVE_COL_NAME1));
+      
Assertions.assertTrue(exception.getMessage().contains("`gravitino.identifier`"));
       MetadataObject[] objects = 
metalake.getTag(tagName).associatedObjects().objects();
       Assertions.assertEquals(1, objects.length);
       Assertions.assertEquals(
-          String.join(".", catalogName, schema, newName, 
HIVE_COL_NAME1).toLowerCase(Locale.ROOT),
+          String.join(".", catalogName, schema, ident.name(), HIVE_COL_NAME1)
+              .toLowerCase(Locale.ROOT),
           objects[0].fullName().toLowerCase(Locale.ROOT));
     } finally {
       metalake.deleteTag(tagName);
     }
   }
 
+  @Test
+  public void testCatalogReregistrationRequiresExternalIdentifierCleanup()

Review Comment:
   [Question] This closes the schema half of the layering gap from the last 
pass - thanks. Does anything cover the table half?
   
   The schema registered here is created empty, so exactly one retained ID is 
exercised. In a real re-registration every table and view under the dropped 
catalog also keeps its own `gravitino.identifier`, and 
`TableMetaService.isSoftDeleted:117` / `ViewMetaService.isSoftDeleted` go 
through the same `JDBCBackend.isSoftDeleted:1037-1049` branch. So after the 
operator clears the schema's identifier and the schema imports with a new ID, 
each `loadTable` should fail again with its own soft-deleted error until that 
table's identifier is cleared too. That is exactly what the schema message asks 
for - "the external schema and all of its tables and views" 
(`OperationDispatcher.java:102`) - but nothing demonstrates the sequence, and 
on an HMS schema with many tables it is the part an operator will spend their 
time on.
   
   Creating one table in the schema before the drop and asserting load -> clear 
-> load for it as well looks cheap here, and would pin the per-object cleanup 
contract that the error message promises.
   
   Verified by: read the new test in full at `CatalogHive2IT.java:1481-1537` 
(`createSchema` with `Collections.emptyMap()` and no table creation); read 
`JDBCBackend.isSoftDeleted:1037-1049`, `TableMetaService.isSoftDeleted:111-121` 
and `OperationDispatcher.softDeletedExternalIdentifierException:96-124` on this 
branch.



-- 
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: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to