This is an automated email from the ASF dual-hosted git repository. github-actions[bot] pushed a commit to branch cherry-pick-4b529ce4-to-branch-1.3 in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit b4c135b313dcfff1178181e5933d8f3e7dffeabb Author: geyanggang <[email protected]> AuthorDate: Mon Aug 31 20:20:40 2026 +0800 [#12734] fix(flink-connector): skip no-op table alter to avoid updates must not be empty (#12735) ### What changes were proposed in this pull request? Skip forwarding an empty TableChange list to Gravitino when altering a table through the Flink connector. This covers all three code paths that could produce an empty change set: GravitinoHiveCatalog.applyGenericTableAlter (generic tables with no property/comment diff) BaseCatalog.alterTable(tablePath, newTable, ignoreIfNotExists) (comment-only alter with unchanged comment) BaseCatalog.alterTable(tablePath, newTable, tableChanges, ignoreIfNotExists) (empty Flink tableChanges) A new private helper BaseCatalog.alterGravitinoTable centralizes the empty-change guard for the native paths and only invalidates the native cache when an alter is actually performed. ### Why are the changes needed? When an ALTER TABLE results in no effective change, the connector produced an empty update list and called TableCatalog.alterTable. Server-side TableUpdatesRequest.validate() rejects empty updates with IllegalArgumentException: updates must not be empty, failing an effectively no-op alter (for example an idempotent ALTER TABLE on job startup). Fix: #12734 ### Does this PR introduce _any_ user-facing change? No new APIs or property keys. Behavior change: an ALTER TABLE that results in no actual change now succeeds as a no-op instead of failing. ### How was this patch tested? Added unit tests: TestGravitinoHiveCatalog: no-op generic alter is skipped; property change is forwarded. TestBaseCatalog: empty tableChanges skipped; comment-only unchanged skipped; comment change forwarded. Ran ./gradlew :flink-connector:flink-common:test --tests "org.apache.gravitino.flink.connector.catalog.TestBaseCatalog" --tests "org.apache.gravitino.flink.connector.hive.TestGravitinoHiveCatalog" -PskipITs — all pass. Spotless applied. # Conflicts: # flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java # flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java --- .../flink/connector/catalog/BaseCatalog.java | 40 ++++- .../flink/connector/hive/GravitinoHiveCatalog.java | 12 +- .../flink/connector/catalog/TestBaseCatalog.java | 93 +++++++++++ .../connector/hive/TestGravitinoHiveCatalog.java | 174 +++++++++++++++++++++ .../paimon/TestGravitinoPaimonCatalog.java | 58 ++++++- 5 files changed, 362 insertions(+), 15 deletions(-) diff --git a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java index 59874aff4a..ae806dcdbd 100644 --- a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java +++ b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/catalog/BaseCatalog.java @@ -528,11 +528,11 @@ public abstract class BaseCatalog extends AbstractCatalog { throw new CatalogException(e); } } else { - catalog() - .asTableCatalog() - .alterTable(identifier, getGravitinoTableChanges(existingTable, newTable)); - // Invalidate native catalog cache after successful alter - invalidateTable(tablePath); + TableChange[] changes = getGravitinoTableChanges(existingTable, newTable); + if (alterGravitinoTable(identifier, changes)) { + // Invalidate native catalog cache after successful alter + invalidateTable(tablePath); + } } } @@ -582,9 +582,11 @@ public abstract class BaseCatalog extends AbstractCatalog { throw new CatalogException(e); } } else { - catalog().asTableCatalog().alterTable(identifier, getGravitinoTableChanges(tableChanges)); - // Invalidate native catalog cache after successful alter - invalidateTable(tablePath); + TableChange[] changes = getGravitinoTableChanges(tableChanges); + if (alterGravitinoTable(identifier, changes)) { + // Invalidate native catalog cache after successful alter + invalidateTable(tablePath); + } } } @@ -924,6 +926,28 @@ public abstract class BaseCatalog extends AbstractCatalog { } } + /** + * Applies the given table changes to the underlying Gravitino table, skipping the call when there + * is nothing to change. + * + * <p>When {@code changes} is empty the resolved table already matches the existing one (for + * example, re-applying the same options or a comment-only alter with an unchanged comment). + * Gravitino's {@code TableUpdatesRequest.validate} rejects an empty update list with "updates + * must not be empty", so a no-op alter must be skipped rather than forwarded. + * + * @param identifier the identifier of the table to alter + * @param changes the Gravitino table changes to apply + * @return {@code true} if the alter was forwarded to Gravitino, {@code false} if it was skipped + * because there was nothing to change + */ + private boolean alterGravitinoTable(NameIdentifier identifier, TableChange[] changes) { + if (changes.length == 0) { + return false; + } + catalog().asTableCatalog().alterTable(identifier, changes); + return true; + } + @VisibleForTesting static TableChange[] getGravitinoTableChanges( CatalogBaseTable existingTable, CatalogBaseTable newTable) { diff --git a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java index feb6362fae..551a4a51d9 100644 --- a/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java +++ b/flink-connector/flink-common/src/main/java/org/apache/gravitino/flink/connector/hive/GravitinoHiveCatalog.java @@ -18,6 +18,7 @@ */ package org.apache.gravitino.flink.connector.hive; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.Collections; @@ -281,7 +282,8 @@ public class GravitinoHiveCatalog extends BaseCatalog { } } - private void applyGenericTableAlter( + @VisibleForTesting + void applyGenericTableAlter( ObjectPath tablePath, Table existingTable, ResolvedCatalogTable newTable) throws TableNotExistException, CatalogException { NameIdentifier identifier = @@ -311,6 +313,14 @@ public class GravitinoHiveCatalog extends BaseCatalog { } }); + // When the resolved table is identical to the existing one (for example, re-applying the same + // options), no TableChange is produced. Skip the alter call in that case: Gravitino's + // TableUpdatesRequest.validate rejects an empty update list with "updates must not be empty", + // and a no-op alter should succeed rather than fail. + if (changes.isEmpty()) { + return; + } + try { catalog().asTableCatalog().alterTable(identifier, changes.toArray(new TableChange[0])); } catch (NoSuchTableException e) { diff --git a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java index e24dc13c6c..dba8dc8a2a 100644 --- a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java +++ b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/catalog/TestBaseCatalog.java @@ -36,6 +36,11 @@ import org.apache.flink.table.catalog.Column; import org.apache.flink.table.catalog.ResolvedCatalogView; import org.apache.flink.table.catalog.ResolvedSchema; import org.apache.flink.table.catalog.TableChange; +<<<<<<< HEAD +======= +import org.apache.flink.table.catalog.exceptions.CatalogException; +import org.apache.flink.table.catalog.exceptions.TableNotExistException; +>>>>>>> 4b529ce4d ([#12734] fix(flink-connector): skip no-op table alter to avoid updates must not be empty (#12735)) import org.apache.gravitino.Catalog; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; @@ -372,12 +377,86 @@ public class TestBaseCatalog { } } + @Test + public void testAlterTableWithEmptyTableChangesSkipsAlterCall() throws Exception { + // Flink may invoke alterTable with an empty change list (a no-op alter). The connector must + // not forward an empty update list to Gravitino, which would fail server-side with + // "updates must not be empty". + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + + Schema schema = Schema.newBuilder().column("id", DataTypes.INT()).build(); + CatalogBaseTable table = + DefaultCatalogCompat.INSTANCE.createCatalogTable( + schema, "comment", ImmutableList.of(), ImmutableMap.of("key", "value")); + + TestableBaseCatalog catalog = + new TestableBaseCatalog(Mockito.mock(AbstractCatalog.class), gravitinoCatalog, table); + + catalog.alterTable(new ObjectPath("db", "tbl"), table, Collections.emptyList(), false); + + Mockito.verify(tableCatalog, Mockito.never()).alterTable(Mockito.any(), Mockito.any()); + } + + @Test + public void testAlterTableWithCommentOnlyAndUnchangedCommentSkipsAlterCall() throws Exception { + // The two-argument alterTable diffs only the comment. When the comment is unchanged no + // TableChange is produced, so the connector must skip the alter call instead of forwarding an + // empty update list. + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + + Schema schema = Schema.newBuilder().column("id", DataTypes.INT()).build(); + CatalogBaseTable table = + DefaultCatalogCompat.INSTANCE.createCatalogTable( + schema, "comment", ImmutableList.of(), ImmutableMap.of("key", "value")); + + TestableBaseCatalog catalog = + new TestableBaseCatalog(Mockito.mock(AbstractCatalog.class), gravitinoCatalog, table); + + catalog.alterTable(new ObjectPath("db", "tbl"), table, false); + + Mockito.verify(tableCatalog, Mockito.never()).alterTable(Mockito.any(), Mockito.any()); + } + + @Test + public void testAlterTableWithCommentChangeForwardsAlterCall() throws Exception { + // A comment change produces a TableChange, so the alter must be forwarded to Gravitino. + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + + Schema schema = Schema.newBuilder().column("id", DataTypes.INT()).build(); + CatalogBaseTable existingTable = + DefaultCatalogCompat.INSTANCE.createCatalogTable( + schema, "old comment", ImmutableList.of(), ImmutableMap.of("key", "value")); + CatalogBaseTable newTable = + DefaultCatalogCompat.INSTANCE.createCatalogTable( + schema, "new comment", ImmutableList.of(), ImmutableMap.of("key", "value")); + + TestableBaseCatalog catalog = + new TestableBaseCatalog( + Mockito.mock(AbstractCatalog.class), gravitinoCatalog, existingTable); + + catalog.alterTable(new ObjectPath("db", "tbl"), newTable, false); + + Mockito.verify(tableCatalog, Mockito.times(1)).alterTable(Mockito.any(), Mockito.any()); + } + private static class TestableBaseCatalog extends BaseCatalog { private final AbstractCatalog delegate; private final Catalog gravitinoCatalog; + private final CatalogBaseTable existingTable; TestableBaseCatalog(AbstractCatalog delegate, Catalog gravitinoCatalog) { + this(delegate, gravitinoCatalog, null); + } + + TestableBaseCatalog( + AbstractCatalog delegate, Catalog gravitinoCatalog, CatalogBaseTable existingTable) { super( "test", Collections.emptyMap(), @@ -386,6 +465,7 @@ public class TestBaseCatalog { Mockito.mock(PartitionConverter.class)); this.delegate = delegate; this.gravitinoCatalog = gravitinoCatalog; + this.existingTable = existingTable; } @Override @@ -397,5 +477,18 @@ public class TestBaseCatalog { protected Catalog catalog() { return gravitinoCatalog; } + + @Override + public CatalogBaseTable getTable(ObjectPath tablePath) throws TableNotExistException { + if (existingTable != null) { + return existingTable; + } + return super.getTable(tablePath); + } + + @Override + protected void invalidateTable(ObjectPath tablePath) { + // No-op: the native cache is not exercised in these unit tests. + } } } diff --git a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java new file mode 100644 index 0000000000..4eea1d8bee --- /dev/null +++ b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/hive/TestGravitinoHiveCatalog.java @@ -0,0 +1,174 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.gravitino.flink.connector.hive; + +import com.google.common.collect.ImmutableMap; +import java.util.Collections; +import java.util.Map; +import org.apache.flink.table.api.DataTypes; +import org.apache.flink.table.api.Schema; +import org.apache.flink.table.catalog.AbstractCatalog; +import org.apache.flink.table.catalog.CatalogTable; +import org.apache.flink.table.catalog.Column; +import org.apache.flink.table.catalog.ObjectPath; +import org.apache.flink.table.catalog.ResolvedCatalogTable; +import org.apache.flink.table.catalog.ResolvedSchema; +import org.apache.flink.table.catalog.exceptions.CatalogException; +import org.apache.gravitino.Catalog; +import org.apache.gravitino.exceptions.ForbiddenException; +import org.apache.gravitino.flink.connector.PartitionConverter; +import org.apache.gravitino.flink.connector.SchemaAndTablePropertiesConverter; +import org.apache.gravitino.flink.connector.utils.DefaultCatalogCompat; +import org.apache.gravitino.rel.Table; +import org.apache.gravitino.rel.TableCatalog; +import org.apache.hadoop.hive.conf.HiveConf; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +public class TestGravitinoHiveCatalog { + + @Test + public void testGetTableThrowsCatalogExceptionWhenForbidden() throws Exception { + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + ForbiddenException forbiddenException = new ForbiddenException("denied"); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + Mockito.when(tableCatalog.loadTable(Mockito.any())).thenThrow(forbiddenException); + TestableGravitinoHiveCatalog catalog = new TestableGravitinoHiveCatalog(gravitinoCatalog); + + CatalogException catalogException = + Assertions.assertThrows( + CatalogException.class, () -> catalog.getTable(new ObjectPath("db", "tbl"))); + + Assertions.assertSame(forbiddenException, catalogException.getCause()); + } + + @Test + public void testGenericTableAlterSkipsCallWhenNoChanges() throws Exception { + // Existing table and the resolved new table describe the same state (same properties, same + // comment), so no TableChange is produced. The connector must not forward an empty update + // list to Gravitino, which would fail server-side with "updates must not be empty". + Map<String, String> sameProperties = + ImmutableMap.of("flink.connector", "kafka", "is_generic", "true"); + + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + + Table existingTable = Mockito.mock(Table.class); + Mockito.when(existingTable.properties()).thenReturn(sameProperties); + Mockito.when(existingTable.comment()).thenReturn("same comment"); + + ResolvedCatalogTable newTable = resolvedTable("same comment"); + + TestableGravitinoHiveCatalog catalog = + new TestableGravitinoHiveCatalog(gravitinoCatalog, sameProperties); + + catalog.applyGenericTableAlter(new ObjectPath("db", "tbl"), existingTable, newTable); + + // The alter call is skipped entirely because there is nothing to change. + Mockito.verify(tableCatalog, Mockito.never()).alterTable(Mockito.any(), Mockito.any()); + } + + @Test + public void testGenericTableAlterCallsAlterWhenPropertiesChange() throws Exception { + // The resolved new table changes a property, so the connector must forward the update. + Map<String, String> currentProperties = + ImmutableMap.of("flink.connector", "kafka", "is_generic", "true"); + Map<String, String> updatedProperties = + ImmutableMap.of( + "flink.connector", "kafka", "flink.topic", "new-topic", "is_generic", "true"); + + Catalog gravitinoCatalog = Mockito.mock(Catalog.class); + TableCatalog tableCatalog = Mockito.mock(TableCatalog.class); + Mockito.when(gravitinoCatalog.asTableCatalog()).thenReturn(tableCatalog); + + Table existingTable = Mockito.mock(Table.class); + Mockito.when(existingTable.properties()).thenReturn(currentProperties); + Mockito.when(existingTable.comment()).thenReturn("comment"); + + ResolvedCatalogTable newTable = resolvedTable("comment"); + + TestableGravitinoHiveCatalog catalog = + new TestableGravitinoHiveCatalog(gravitinoCatalog, updatedProperties); + + catalog.applyGenericTableAlter(new ObjectPath("db", "tbl"), existingTable, newTable); + + // A real change was present, so the alter call is forwarded to Gravitino. + Mockito.verify(tableCatalog, Mockito.times(1)).alterTable(Mockito.any(), Mockito.any()); + } + + private static ResolvedCatalogTable resolvedTable(String comment) { + Schema schema = Schema.newBuilder().column("id", DataTypes.INT()).build(); + CatalogTable table = + DefaultCatalogCompat.INSTANCE.createCatalogTable( + schema, comment, Collections.emptyList(), Collections.emptyMap()); + ResolvedSchema resolvedSchema = + new ResolvedSchema( + Collections.singletonList(Column.physical("id", DataTypes.INT())), + Collections.emptyList(), + null); + return new ResolvedCatalogTable(table, resolvedSchema); + } + + private static class TestableGravitinoHiveCatalog extends GravitinoHiveCatalog { + private final Catalog gravitinoCatalog; + private final Map<String, String> genericTableProperties; + + TestableGravitinoHiveCatalog(Catalog gravitinoCatalog) { + this(gravitinoCatalog, Collections.emptyMap()); + } + + TestableGravitinoHiveCatalog( + Catalog gravitinoCatalog, Map<String, String> genericTableProperties) { + super( + "test", + "default", + Collections.emptyMap(), + Mockito.mock(SchemaAndTablePropertiesConverter.class), + Mockito.mock(PartitionConverter.class), + hiveConf(), + null); + this.gravitinoCatalog = gravitinoCatalog; + this.genericTableProperties = genericTableProperties; + } + + @Override + protected AbstractCatalog realCatalog() { + return Mockito.mock(AbstractCatalog.class); + } + + @Override + protected Catalog catalog() { + return gravitinoCatalog; + } + + @Override + protected Map<String, String> toGravitinoGenericTableProperties(ResolvedCatalogTable table) { + return genericTableProperties; + } + + private static HiveConf hiveConf() { + HiveConf hiveConf = new HiveConf(); + hiveConf.set("hive.metastore.uris", "thrift://localhost:9083"); + return hiveConf; + } + } +} diff --git a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java index 3e29e30e2e..3d6f9e6d98 100644 --- a/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java +++ b/flink-connector/flink-common/src/test/java/org/apache/gravitino/flink/connector/paimon/TestGravitinoPaimonCatalog.java @@ -373,13 +373,15 @@ public class TestGravitinoPaimonCatalog { TestablePaimonCatalog cat = new TestablePaimonCatalog(mockFlinkCatalog, mockCatalog); ObjectPath path = new ObjectPath("mydb", "mytable"); + org.apache.flink.table.api.Schema schema = + org.apache.flink.table.api.Schema.newBuilder().column("id", DataTypes.INT()).build(); + // The existing table carries an old comment so the alter produces a real TableChange + // (comment update) rather than a no-op. + CatalogTable existingFlinkTable = + CatalogTable.of(schema, "old comment", Collections.emptyList(), Collections.emptyMap()); CatalogTable newTable = - CatalogTable.of( - org.apache.flink.table.api.Schema.newBuilder().column("id", DataTypes.INT()).build(), - "new comment", - Collections.emptyList(), - Collections.emptyMap()); - when(mockFlinkCatalog.getTable(path)).thenReturn(newTable); + CatalogTable.of(schema, "new comment", Collections.emptyList(), Collections.emptyMap()); + when(mockFlinkCatalog.getTable(path)).thenReturn(existingFlinkTable); cat.alterTable(path, newTable, false); @@ -387,6 +389,50 @@ public class TestGravitinoPaimonCatalog { verify(mockInnerCatalog).invalidateTable(Identifier.create("mydb", "mytable")); } + /** + * Verifies that a no-op Paimon alterTable (existing and new tables identical) neither forwards + * the alter to Gravitino nor invalidates the native cache. + */ + @Test + public void testAlterTableNoOpDoesNotInvalidateNativeCache() throws Exception { + org.apache.paimon.catalog.Catalog mockInnerCatalog = + mock(org.apache.paimon.catalog.Catalog.class); + FlinkCatalog mockFlinkCatalog = mock(FlinkCatalog.class); + when(mockFlinkCatalog.catalog()).thenReturn(mockInnerCatalog); + + Catalog mockCatalog = mock(Catalog.class); + TableCatalog mockTableCatalog = mock(TableCatalog.class); + when(mockCatalog.asTableCatalog()).thenReturn(mockTableCatalog); + + Table existingTable = mock(Table.class); + org.apache.gravitino.rel.Column existingColumn = + org.apache.gravitino.rel.Column.of( + "id", org.apache.gravitino.rel.types.Types.IntegerType.get()); + when(existingTable.columns()) + .thenReturn(new org.apache.gravitino.rel.Column[] {existingColumn}); + when(existingTable.index()).thenReturn(new Index[0]); + when(existingTable.properties()).thenReturn(Collections.emptyMap()); + when(existingTable.distribution()).thenReturn(null); + when(existingTable.partitioning()).thenReturn(Transforms.EMPTY_TRANSFORM); + when(existingTable.comment()).thenReturn("same comment"); + when(mockTableCatalog.loadTable(any())).thenReturn(existingTable); + + TestablePaimonCatalog cat = new TestablePaimonCatalog(mockFlinkCatalog, mockCatalog); + ObjectPath path = new ObjectPath("mydb", "mytable"); + org.apache.flink.table.api.Schema schema = + org.apache.flink.table.api.Schema.newBuilder().column("id", DataTypes.INT()).build(); + // The existing table and the alter target share the same comment, so no TableChange is + // produced and the alter must be skipped. + CatalogTable sameTable = + CatalogTable.of(schema, "same comment", Collections.emptyList(), Collections.emptyMap()); + when(mockFlinkCatalog.getTable(path)).thenReturn(sameTable); + + cat.alterTable(path, sameTable, false); + + verify(mockTableCatalog, never()).alterTable(any(), any()); + verify(mockInnerCatalog, never()).invalidateTable(any()); + } + /** Verifies that successful Paimon dropTable invalidates the native cache. */ @Test public void testDropTableInvalidatesNativeCacheAfterSuccessfulPurge() throws Exception {
