This is an automated email from the ASF dual-hosted git repository.
jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 89e7997436 [#8632] feature(alterTable): add core workflow for
renameTable with newSchema (#8635)
89e7997436 is described below
commit 89e799743688f8313103e5acc3560c23999e2d19
Author: mchades <[email protected]>
AuthorDate: Tue Sep 23 15:13:52 2025 +0800
[#8632] feature(alterTable): add core workflow for renameTable with
newSchema (#8635)
### What changes were proposed in this pull request?
dd core workflow for renameTable with newSchema
### Why are the changes needed?
Fix: #8632
### Does this PR introduce _any_ user-facing change?
no, it is backward compatibility
### How was this patch tested?
tests added
---
.../java/org/apache/gravitino/rel/TableChange.java | 33 ++++-
.../java/org/apache/gravitino/TestTableChange.java | 6 +
.../catalog/hive/HiveCatalogOperations.java | 2 +
.../catalog/jdbc/JdbcCatalogOperations.java | 2 +
.../org/apache/gravitino/client/DTOConverters.java | 3 +-
.../gravitino/dto/requests/TableUpdateRequest.java | 21 ++-
.../dto/requests/TestTableUpdatesRequest.java | 22 ++-
.../gravitino/catalog/CapabilityHelpers.java | 7 +-
.../catalog/TableOperationDispatcher.java | 66 ++++++---
.../apache/gravitino/hook/TableHookDispatcher.java | 1 +
.../relational/mapper/TableColumnMapper.java | 4 +
.../mapper/TableColumnSQLProviderFactory.java | 5 +
.../storage/relational/mapper/TableMetaMapper.java | 4 +-
.../mapper/TableMetaSQLProviderFactory.java | 6 +-
.../provider/base/TableColumnBaseSQLProvider.java | 8 ++
.../provider/base/TableMetaBaseSQLProvider.java | 6 +-
.../relational/service/TableColumnMetaService.java | 7 +
.../relational/service/TableMetaService.java | 14 +-
.../storage/relational/utils/POConverters.java | 7 +-
.../relational/service/TestTableMetaService.java | 152 +++++++++++++++++++++
.../storage/relational/utils/TestPOConverters.java | 3 +-
docs/manage-relational-metadata-using-gravitino.md | 28 ++--
docs/open-api/tables.yaml | 7 +-
23 files changed, 360 insertions(+), 54 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/rel/TableChange.java
b/api/src/main/java/org/apache/gravitino/rel/TableChange.java
index ea5849eb6e..03c05622b7 100644
--- a/api/src/main/java/org/apache/gravitino/rel/TableChange.java
+++ b/api/src/main/java/org/apache/gravitino/rel/TableChange.java
@@ -23,6 +23,7 @@ package org.apache.gravitino.rel;
import com.google.common.base.Preconditions;
import java.util.Arrays;
import java.util.Objects;
+import java.util.Optional;
import org.apache.gravitino.annotation.Evolving;
import org.apache.gravitino.rel.expressions.Expression;
import org.apache.gravitino.rel.indexes.Index.IndexType;
@@ -44,6 +45,17 @@ public interface TableChange {
return new RenameTable(newName);
}
+ /**
+ * Create a TableChange for renaming a table, possibly moving it to a new
schema.
+ *
+ * @param newName the new table name
+ * @param newSchemaName the new schema name, or null to leave the schema
unchanged
+ * @return A TableChange for the rename.
+ */
+ static TableChange rename(String newName, String newSchemaName) {
+ return new RenameTable(newName, Optional.ofNullable(newSchemaName));
+ }
+
/**
* Create a TableChange for updating the comment.
*
@@ -467,9 +479,15 @@ public interface TableChange {
/** A TableChange to rename a table. */
final class RenameTable implements TableChange {
private final String newName;
+ private final Optional<String> newSchemaName;
private RenameTable(String newName) {
+ this(newName, Optional.empty());
+ }
+
+ private RenameTable(String newName, Optional<String> newSchemaName) {
this.newName = newName;
+ this.newSchemaName = newSchemaName;
}
/**
@@ -481,6 +499,15 @@ public interface TableChange {
return newName;
}
+ /**
+ * Retrieves the new schema name for the table, if provided.
+ *
+ * @return An Optional containing the new schema of the table, or empty if
not provided.
+ */
+ public Optional<String> getNewSchemaName() {
+ return newSchemaName;
+ }
+
/**
* Compares this RenameTable instance with another object for equality.
The comparison is based
* on the new name of the table.
@@ -493,7 +520,7 @@ public interface TableChange {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RenameTable that = (RenameTable) o;
- return newName.equals(that.newName);
+ return newName.equals(that.newName) &&
newSchemaName.equals(that.newSchemaName);
}
/**
@@ -504,7 +531,7 @@ public interface TableChange {
*/
@Override
public int hashCode() {
- return newName.hashCode();
+ return Objects.hash(newName, newSchemaName);
}
/**
@@ -515,7 +542,7 @@ public interface TableChange {
*/
@Override
public String toString() {
- return "RENAMETABLE " + newName;
+ return "RENAMETABLE " + newSchemaName.map(s -> s + "." +
newName).orElse(newName);
}
}
diff --git a/api/src/test/java/org/apache/gravitino/TestTableChange.java
b/api/src/test/java/org/apache/gravitino/TestTableChange.java
index 8f32c238f7..47a0e23c35 100644
--- a/api/src/test/java/org/apache/gravitino/TestTableChange.java
+++ b/api/src/test/java/org/apache/gravitino/TestTableChange.java
@@ -307,6 +307,12 @@ public class TestTableChange {
assertFalse(change1.equals(change2));
assertFalse(change2.equals(change1));
assertNotEquals(change1.hashCode(), change2.hashCode());
+
+ String schemaNameA = "Schema A";
+ RenameTable change3 = (RenameTable) TableChange.rename(nameA, schemaNameA);
+ RenameTable change4 = (RenameTable) TableChange.rename(nameB, schemaNameA);
+
+ assertNotEquals(change3, change4);
}
@Test
diff --git
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
index 482dbc67da..8334ff0dad 100644
---
a/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
+++
b/catalogs/catalog-hive/src/main/java/org/apache/gravitino/catalog/hive/HiveCatalogOperations.java
@@ -968,6 +968,8 @@ public class HiveCatalogOperations implements
CatalogOperations, SupportsSchemas
private void doRenameTable(
org.apache.hadoop.hive.metastore.api.Table hiveTable,
TableChange.RenameTable change) {
+ Preconditions.checkArgument(
+ !change.getNewSchemaName().isPresent(), "Does not support rename
schema yet");
hiveTable.setTableName(change.getNewName());
}
diff --git
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
index 3b318150c7..7c505b08f5 100644
---
a/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
+++
b/catalogs/catalog-jdbc-common/src/main/java/org/apache/gravitino/catalog/jdbc/JdbcCatalogOperations.java
@@ -505,6 +505,8 @@ public class JdbcCatalogOperations implements
CatalogOperations, SupportsSchemas
*/
private Table renameTable(NameIdentifier tableIdent, TableChange.RenameTable
renameTable)
throws NoSuchTableException, IllegalArgumentException {
+ Preconditions.checkArgument(
+ !renameTable.getNewSchemaName().isPresent(), "Does not support rename
schema yet");
String databaseName =
NameIdentifier.of(tableIdent.namespace().levels()).name();
tableOperation.rename(databaseName, tableIdent.name(),
renameTable.getNewName());
return loadTable(NameIdentifier.of(tableIdent.namespace(),
renameTable.getNewName()));
diff --git
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
index 4657644e1f..2cf7acda4e 100644
---
a/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
+++
b/clients/client-java/src/main/java/org/apache/gravitino/client/DTOConverters.java
@@ -197,8 +197,9 @@ class DTOConverters {
static TableUpdateRequest toTableUpdateRequest(TableChange change) {
if (change instanceof TableChange.RenameTable) {
+ TableChange.RenameTable renameTable = (TableChange.RenameTable) change;
return new TableUpdateRequest.RenameTableRequest(
- ((TableChange.RenameTable) change).getNewName());
+ renameTable.getNewName(),
renameTable.getNewSchemaName().orElse(null));
} else if (change instanceof TableChange.UpdateComment) {
return new TableUpdateRequest.UpdateTableCommentRequest(
diff --git
a/common/src/main/java/org/apache/gravitino/dto/requests/TableUpdateRequest.java
b/common/src/main/java/org/apache/gravitino/dto/requests/TableUpdateRequest.java
index db1702ce6d..c1f7acd4a4 100644
---
a/common/src/main/java/org/apache/gravitino/dto/requests/TableUpdateRequest.java
+++
b/common/src/main/java/org/apache/gravitino/dto/requests/TableUpdateRequest.java
@@ -99,19 +99,23 @@ public interface TableUpdateRequest extends RESTRequest {
/** Represents a request to rename a table. */
@EqualsAndHashCode
@ToString
+ @Getter
class RenameTableRequest implements TableUpdateRequest {
- @Getter
@JsonProperty("newName")
private final String newName;
+ @JsonProperty("newSchemaName")
+ @Nullable
+ private final String newSchemaName;
+
/**
* Constructor for RenameTableRequest.
*
* @param newName the new name of the table
*/
public RenameTableRequest(String newName) {
- this.newName = newName;
+ this(newName, null);
}
/** Default constructor for Jackson deserialization. */
@@ -119,6 +123,17 @@ public interface TableUpdateRequest extends RESTRequest {
this(null);
}
+ /**
+ * Constructor for RenameTableRequest.
+ *
+ * @param newName the new name of the table
+ * @param newSchemaName the new schema name of the table, null if not
changing schema
+ */
+ public RenameTableRequest(String newName, @Nullable String newSchemaName) {
+ this.newName = newName;
+ this.newSchemaName = newSchemaName;
+ }
+
/**
* Validates the request.
*
@@ -137,7 +152,7 @@ public interface TableUpdateRequest extends RESTRequest {
*/
@Override
public TableChange tableChange() {
- return TableChange.rename(newName);
+ return TableChange.rename(newName, newSchemaName);
}
}
diff --git
a/common/src/test/java/org/apache/gravitino/dto/requests/TestTableUpdatesRequest.java
b/common/src/test/java/org/apache/gravitino/dto/requests/TestTableUpdatesRequest.java
index 1395af798b..9276383066 100644
---
a/common/src/test/java/org/apache/gravitino/dto/requests/TestTableUpdatesRequest.java
+++
b/common/src/test/java/org/apache/gravitino/dto/requests/TestTableUpdatesRequest.java
@@ -66,7 +66,8 @@ public class TestTableUpdatesRequest {
+ " \"updates\": [\n"
+ " {\n"
+ " \"@type\": \"rename\",\n"
- + " \"newName\": \"newTable\"\n"
+ + " \"newName\": \"newTable\",\n"
+ + " \"newSchemaName\": null\n"
+ " },\n"
+ " {\n"
+ " \"@type\": \"updateComment\",\n"
@@ -139,6 +140,25 @@ public class TestTableUpdatesRequest {
Assertions.assertEquals(
JsonUtils.objectMapper().readTree(expected),
JsonUtils.objectMapper().readTree(jsonString));
+ // validate backward compatibility for renameTable without newSchemaName
+ String renameTableJsonString =
+ "{\n"
+ + " \"updates\": [\n"
+ + " {\n"
+ + " \"@type\": \"rename\",\n"
+ + " \"newName\": \"newTable\"\n"
+ + " }\n"
+ + " ]\n"
+ + "}";
+ TableUpdatesRequest renameTableRequest =
+ JsonUtils.objectMapper().readValue(renameTableJsonString,
TableUpdatesRequest.class);
+ Assertions.assertEquals(1, renameTableRequest.getUpdates().size());
+ TableUpdateRequest renameTable = renameTableRequest.getUpdates().get(0);
+ Assertions.assertInstanceOf(TableUpdateRequest.RenameTableRequest.class,
renameTable);
+ Assertions.assertEquals(
+ "newTable", ((TableUpdateRequest.RenameTableRequest)
renameTable).getNewName());
+ Assertions.assertNull(((TableUpdateRequest.RenameTableRequest)
renameTable).getNewSchemaName());
+
// test validate blank property value
TableUpdateRequest.SetTablePropertyRequest setTablePropertyRequest =
new TableUpdateRequest.SetTablePropertyRequest("key", " ");
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
index 172d4b89d8..fe4659c28e 100644
--- a/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
+++ b/core/src/main/java/org/apache/gravitino/catalog/CapabilityHelpers.java
@@ -325,8 +325,13 @@ public class CapabilityHelpers {
TableChange.RenameTable renameTable, Capability capabilities) {
String newName =
applyCaseSensitiveOnName(Capability.Scope.TABLE,
renameTable.getNewName(), capabilities);
+ String newSchemaName =
+ renameTable
+ .getNewSchemaName()
+ .map(s -> applyCaseSensitiveOnName(Capability.Scope.SCHEMA, s,
capabilities))
+ .orElse(null);
applyNameSpecification(Capability.Scope.TABLE, newName, capabilities);
- return TableChange.rename(newName);
+ return TableChange.rename(newName, newSchemaName);
}
private static TableChange applyCapabilities(
diff --git
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
index f110dc02bb..a777f2a511 100644
---
a/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/catalog/TableOperationDispatcher.java
@@ -23,6 +23,7 @@ import static
org.apache.gravitino.catalog.CapabilityHelpers.applyCapabilities;
import static
org.apache.gravitino.catalog.PropertiesMetadataHelpers.validatePropertyForCreate;
import static
org.apache.gravitino.rel.expressions.transforms.Transforms.EMPTY_TRANSFORM;
import static
org.apache.gravitino.utils.NameIdentifierUtil.getCatalogIdentifier;
+import static
org.apache.gravitino.utils.NameIdentifierUtil.getSchemaIdentifier;
import com.google.common.base.Objects;
import com.google.common.collect.Lists;
@@ -62,7 +63,7 @@ import
org.apache.gravitino.rel.expressions.transforms.Transform;
import org.apache.gravitino.rel.indexes.Index;
import org.apache.gravitino.rel.indexes.Indexes;
import org.apache.gravitino.storage.IdGenerator;
-import org.apache.gravitino.utils.NameIdentifierUtil;
+import org.apache.gravitino.utils.NamespaceUtil;
import org.apache.gravitino.utils.PrincipalUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -199,16 +200,31 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
throws NoSuchTableException, IllegalArgumentException {
validateAlterProperties(ident,
HasPropertyMetadata::tablePropertiesMetadata, changes);
- // Check if there exist TableChange.RenameTable in the changes, if so, we
need to TreeLock of
- // write on the new table name, or use the read lock on the table instead.
- boolean containsRenameTable =
- Arrays.stream(changes).anyMatch(c -> c instanceof
TableChange.RenameTable);
- NameIdentifier nameIdentifierForLock =
- containsRenameTable ? NameIdentifier.of(ident.namespace().levels()) :
ident;
+ // use the read lock on the table if there does not exist
TableChange.RenameTable in the
+ // changes, or:
+ // 1. if the TableChange.RenameTable change the schema name, we need to
acquire write lock on
+ // the catalog,
+ // because the treeLock tool currently does not support acquiring write
locks on two tables
+ // at the same time.
+ // 2. if the TableChange.RenameTable only change the table name, we need
to acquire write lock
+ // to on the schema.
+ NameIdentifier nameIdentifierForLock = ident;
+ String schemaName = ident.namespace().level(2);
+ for (TableChange change : changes) {
+ if (change instanceof TableChange.RenameTable) {
+ TableChange.RenameTable rename = (TableChange.RenameTable) change;
+ if (rename.getNewSchemaName().isPresent()
+ && !rename.getNewSchemaName().get().equals(schemaName)) {
+ nameIdentifierForLock = getCatalogIdentifier(ident);
+ break;
+ }
+ nameIdentifierForLock = getSchemaIdentifier(ident);
+ }
+ }
return TreeLockUtils.doWithTreeLock(
nameIdentifierForLock,
- LockType.WRITE,
+ nameIdentifierForLock.equals(ident) ? LockType.READ : LockType.WRITE,
() -> {
NameIdentifier catalogIdent = getCatalogIdentifier(ident);
Table alteredTable =
@@ -251,20 +267,16 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
TableEntity.class,
TABLE,
tableEntity -> {
- String newName =
- Arrays.stream(changes)
- .filter(c -> c instanceof
TableChange.RenameTable)
- .map(c -> ((TableChange.RenameTable)
c).getNewName())
- .reduce((c1, c2) -> c2)
- .orElse(tableEntity.name());
+ Namespace newNamespace = getNewNamespace(ident,
changes);
+
// Update the columns
Pair<Boolean, List<ColumnEntity>>
columnsUpdateResult =
updateColumnsIfNecessary(alteredTable,
tableEntity);
return TableEntity.builder()
.withId(tableEntity.id())
- .withName(newName)
- .withNamespace(ident.namespace())
+ .withName(alteredTable.name())
+ .withNamespace(newNamespace)
.withColumns(columnsUpdateResult.getRight())
.withAuditInfo(
AuditInfo.builder()
@@ -298,7 +310,7 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
*/
@Override
public boolean dropTable(NameIdentifier ident) {
- NameIdentifier schemaIdentifier =
NameIdentifierUtil.getSchemaIdentifier(ident);
+ NameIdentifier schemaIdentifier = getSchemaIdentifier(ident);
return TreeLockUtils.doWithTreeLock(
schemaIdentifier,
LockType.WRITE,
@@ -351,7 +363,7 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
*/
@Override
public boolean purgeTable(NameIdentifier ident) throws
UnsupportedOperationException {
- NameIdentifier schemaIdentifier =
NameIdentifierUtil.getSchemaIdentifier(ident);
+ NameIdentifier schemaIdentifier = getSchemaIdentifier(ident);
NameIdentifier catalogIdent = getCatalogIdentifier(ident);
return TreeLockUtils.doWithTreeLock(
schemaIdentifier,
@@ -390,6 +402,24 @@ public class TableOperationDispatcher extends
OperationDispatcher implements Tab
});
}
+ private Namespace getNewNamespace(NameIdentifier tableIdent, TableChange...
changes) {
+ String schemaName = tableIdent.namespace().level(2);
+ return Arrays.stream(changes)
+ .filter(
+ c ->
+ c instanceof TableChange.RenameTable
+ && ((TableChange.RenameTable)
c).getNewSchemaName().isPresent()
+ && !((TableChange.RenameTable)
c).getNewSchemaName().get().equals(schemaName))
+ .map(
+ c ->
+ NamespaceUtil.ofTable(
+ tableIdent.namespace().level(0),
+ tableIdent.namespace().level(1),
+ ((TableChange.RenameTable) c).getNewSchemaName().get()))
+ .reduce((c1, c2) -> c2)
+ .orElse(tableIdent.namespace());
+ }
+
private EntityCombinedTable importTable(NameIdentifier identifier) {
EntityCombinedTable table = internalLoadTable(identifier);
diff --git
a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
index 3bb6a8da26..5afa9ac533 100644
--- a/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
+++ b/core/src/main/java/org/apache/gravitino/hook/TableHookDispatcher.java
@@ -110,6 +110,7 @@ public class TableHookDispatcher implements TableDispatcher
{
Table alteredTable = dispatcher.alterTable(ident, changes);
if (lastRenameChange != null) {
+ // todo: support rename across different schemas
AuthorizationUtils.authorizationPluginRenamePrivileges(
ident, Entity.EntityType.TABLE, lastRenameChange.getNewName(),
locations);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnMapper.java
index 64859c5921..5782d1c535 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnMapper.java
@@ -39,6 +39,10 @@ public interface TableColumnMapper {
@InsertProvider(type = TableColumnSQLProviderFactory.class, method =
"insertColumnPOs")
void insertColumnPOs(@Param("columnPOs") List<ColumnPO> columnPOs);
+ @UpdateProvider(type = TableColumnSQLProviderFactory.class, method =
"updateSchemaIdByTableId")
+ void updateSchemaIdByTableId(
+ @Param("tableId") Long tableId, @Param("newSchemaId") Long newSchemaId);
+
@UpdateProvider(type = TableColumnSQLProviderFactory.class, method =
"softDeleteColumnsByTableId")
Integer softDeleteColumnsByTableId(@Param("tableId") Long tableId);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnSQLProviderFactory.java
index a0e7655e2b..35b5e01cb8 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableColumnSQLProviderFactory.java
@@ -61,6 +61,11 @@ public class TableColumnSQLProviderFactory {
return getProvider().insertColumnPOs(columnPOs);
}
+ public static String updateSchemaIdByTableId(
+ @Param("tableId") Long tableId, @Param("newSchemaId") Long newSchemaId) {
+ return getProvider().updateSchemaIdByTableId(tableId, newSchemaId);
+ }
+
public static String softDeleteColumnsByTableId(@Param("tableId") Long
tableId) {
return getProvider().softDeleteColumnsByTableId(tableId);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaMapper.java
index 41621027bf..e919e539e9 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaMapper.java
@@ -69,7 +69,9 @@ public interface TableMetaMapper {
@UpdateProvider(type = TableMetaSQLProviderFactory.class, method =
"updateTableMeta")
Integer updateTableMeta(
- @Param("newTableMeta") TablePO newTablePO, @Param("oldTableMeta")
TablePO oldTablePO);
+ @Param("newTableMeta") TablePO newTablePO,
+ @Param("oldTableMeta") TablePO oldTablePO,
+ @Param("newSchemaId") Long newSchemaId);
@UpdateProvider(
type = TableMetaSQLProviderFactory.class,
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaSQLProviderFactory.java
index 664b2423f8..ad16f69ceb 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/TableMetaSQLProviderFactory.java
@@ -82,8 +82,10 @@ public class TableMetaSQLProviderFactory {
}
public static String updateTableMeta(
- @Param("newTableMeta") TablePO newTablePO, @Param("oldTableMeta")
TablePO oldTablePO) {
- return getProvider().updateTableMeta(newTablePO, oldTablePO);
+ @Param("newTableMeta") TablePO newTablePO,
+ @Param("oldTableMeta") TablePO oldTablePO,
+ @Param("newSchemaId") Long newSchemaId) {
+ return getProvider().updateTableMeta(newTablePO, oldTablePO, newSchemaId);
}
public static String softDeleteTableMetasByTableId(@Param("tableId") Long
tableId) {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableColumnBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableColumnBaseSQLProvider.java
index 0b59855023..df3d1a3eaf 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableColumnBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableColumnBaseSQLProvider.java
@@ -66,6 +66,14 @@ public class TableColumnBaseSQLProvider {
+ "</script>";
}
+ public String updateSchemaIdByTableId(
+ @Param("tableId") Long tableId, @Param("newSchemaId") Long newSchemaId) {
+ return "UPDATE "
+ + TableColumnMapper.COLUMN_TABLE_NAME
+ + " SET schema_id = #{newSchemaId}"
+ + " WHERE table_id = #{tableId} AND deleted_at = 0";
+ }
+
public String softDeleteColumnsByTableId(@Param("tableId") Long tableId) {
return "UPDATE "
+ TableColumnMapper.COLUMN_TABLE_NAME
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableMetaBaseSQLProvider.java
index 41f26e2340..83a14d31c5 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/TableMetaBaseSQLProvider.java
@@ -134,13 +134,15 @@ public class TableMetaBaseSQLProvider {
}
public String updateTableMeta(
- @Param("newTableMeta") TablePO newTablePO, @Param("oldTableMeta")
TablePO oldTablePO) {
+ @Param("newTableMeta") TablePO newTablePO,
+ @Param("oldTableMeta") TablePO oldTablePO,
+ @Param("newSchemaId") Long newSchemaId) {
return "UPDATE "
+ TABLE_NAME
+ " SET table_name = #{newTableMeta.tableName},"
+ " metalake_id = #{newTableMeta.metalakeId},"
+ " catalog_id = #{newTableMeta.catalogId},"
- + " schema_id = #{newTableMeta.schemaId},"
+ + " schema_id = #{newSchemaId},"
+ " audit_info = #{newTableMeta.auditInfo},"
+ " current_version = #{newTableMeta.currentVersion},"
+ " last_version = #{newTableMeta.lastVersion},"
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableColumnMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableColumnMetaService.java
index d3dabbe89a..1e4bcc7fc2 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableColumnMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableColumnMetaService.java
@@ -184,6 +184,13 @@ public class TableColumnMetaService {
// If there is no change, directly return
if (columnPOsToInsert.isEmpty()) {
+ // If namespace is changed, just update the schema_id of the columns.
+ if (!newTable.namespace().equals(oldTable.namespace())) {
+ SessionUtils.doWithoutCommit(
+ TableColumnMapper.class,
+ mapper ->
+ mapper.updateSchemaIdByTableId(newTablePO.getTableId(),
newTablePO.getSchemaId()));
+ }
return;
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
index db3646c94a..326ba63b5f 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/TableMetaService.java
@@ -176,10 +176,18 @@ public class TableMetaService {
newTableEntity.id(),
oldTableEntity.id());
+ boolean isSchemaChanged =
!newTableEntity.namespace().equals(oldTableEntity.namespace());
+ Long newSchemaId =
+ isSchemaChanged
+ ? CommonMetaService.getInstance()
+ .getParentEntityIdByNamespace(newTableEntity.namespace())
+ : schemaId;
+
boolean isColumnChanged =
TableColumnMetaService.getInstance().isColumnUpdated(oldTableEntity,
newTableEntity);
TablePO newTablePO =
- POConverters.updateTablePOWithVersion(oldTablePO, newTableEntity,
isColumnChanged);
+ POConverters.updateTablePOWithVersionAndSchemaId(
+ oldTablePO, newTableEntity, isColumnChanged, newSchemaId);
final AtomicInteger updateResult = new AtomicInteger(0);
try {
@@ -188,9 +196,9 @@ public class TableMetaService {
updateResult.set(
SessionUtils.getWithoutCommit(
TableMetaMapper.class,
- mapper -> mapper.updateTableMeta(newTablePO,
oldTablePO))),
+ mapper -> mapper.updateTableMeta(newTablePO, oldTablePO,
newSchemaId))),
() -> {
- if (updateResult.get() > 0 && isColumnChanged) {
+ if (updateResult.get() > 0 && (isColumnChanged ||
isSchemaChanged)) {
TableColumnMetaService.getInstance()
.updateColumnPOsFromTableDiff(oldTableEntity,
newTableEntity, newTablePO);
}
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
index 1376fc39a5..127cb022e8 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java
@@ -409,10 +409,11 @@ public class POConverters {
* @param oldTablePO the old TablePO object
* @param newTable the new TableEntity object
* @param needUpdateVersion whether need to update the version
+ * @param newSchemaId the new schema id
* @return TablePO object with updated version
*/
- public static TablePO updateTablePOWithVersion(
- TablePO oldTablePO, TableEntity newTable, boolean needUpdateVersion) {
+ public static TablePO updateTablePOWithVersionAndSchemaId(
+ TablePO oldTablePO, TableEntity newTable, boolean needUpdateVersion,
Long newSchemaId) {
Long lastVersion;
Long currentVersion;
if (needUpdateVersion) {
@@ -429,7 +430,7 @@ public class POConverters {
.withTableName(newTable.name())
.withMetalakeId(oldTablePO.getMetalakeId())
.withCatalogId(oldTablePO.getCatalogId())
- .withSchemaId(oldTablePO.getSchemaId())
+ .withSchemaId(newSchemaId)
.withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(newTable.auditInfo()))
.withCurrentVersion(currentVersion)
.withLastVersion(lastVersion)
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestTableMetaService.java
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestTableMetaService.java
new file mode 100644
index 0000000000..2f2e20f26f
--- /dev/null
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestTableMetaService.java
@@ -0,0 +1,152 @@
+/*
+ * 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.storage.relational.service;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.exceptions.NoSuchEntityException;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.ColumnEntity;
+import org.apache.gravitino.meta.SchemaEntity;
+import org.apache.gravitino.meta.TableEntity;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.apache.gravitino.rel.types.Types;
+import org.apache.gravitino.storage.RandomIdGenerator;
+import org.apache.gravitino.storage.relational.TestJDBCBackend;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestTableMetaService extends TestJDBCBackend {
+ private final String metalakeName = "metalake_for_table_test";
+ private final AuditInfo auditInfo =
+
AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build();
+
+ @Test
+ public void testUpdateTable() throws IOException {
+ String catalogName = "catalog1";
+ String schemaName = "schema1";
+ createParentEntities(metalakeName, catalogName, schemaName, auditInfo);
+
+ ColumnEntity column1 =
+ ColumnEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName("column1")
+ .withPosition(0)
+ .withComment("comment1")
+ .withDataType(Types.IntegerType.get())
+ .withNullable(true)
+ .withAutoIncrement(false)
+ .withDefaultValue(Literals.integerLiteral(1))
+ .withAuditInfo(auditInfo)
+ .build();
+ TableEntity createdTable =
+ TableEntity.builder()
+ .withId(RandomIdGenerator.INSTANCE.nextId())
+ .withName("table1")
+ .withNamespace(Namespace.of(metalakeName, catalogName, schemaName))
+ .withColumns(List.of(column1))
+ .withAuditInfo(auditInfo)
+ .build();
+ TableMetaService.getInstance().insertTable(createdTable, false);
+
+ // test update table without changing schema name
+ TableEntity updatedTable =
+ TableEntity.builder()
+ .withId(createdTable.id())
+ .withName("table2")
+ .withNamespace(createdTable.namespace())
+ .withColumns(createdTable.columns())
+ .withAuditInfo(auditInfo)
+ .build();
+ Function<TableEntity, TableEntity> updater = oldTable -> updatedTable;
+ TableMetaService.getInstance().updateTable(createdTable.nameIdentifier(),
updater);
+
+ TableEntity retrievedTable =
+
TableMetaService.getInstance().getTableByIdentifier(updatedTable.nameIdentifier());
+ Assertions.assertEquals(updatedTable.id(), retrievedTable.id());
+ Assertions.assertEquals(updatedTable.name(), retrievedTable.name());
+ Assertions.assertEquals(updatedTable.namespace(),
retrievedTable.namespace());
+ Assertions.assertEquals(updatedTable.auditInfo(),
retrievedTable.auditInfo());
+ compareTwoColumns(updatedTable.columns(), retrievedTable.columns());
+ compareTwoColumns(updatedTable.columns(), retrievedTable.columns());
+
+ // test update table with changing schema name to a non-existing schema
+ String newSchemaName = "schema2";
+ TableEntity updatedTable2 =
+ TableEntity.builder()
+ .withId(updatedTable.id())
+ .withName("table3")
+ .withNamespace(Namespace.of(metalakeName, catalogName,
newSchemaName))
+ .withColumns(updatedTable.columns())
+ .withAuditInfo(auditInfo)
+ .build();
+ Function<TableEntity, TableEntity> updater2 = oldTable -> updatedTable2;
+ Exception e =
+ Assertions.assertThrows(
+ NoSuchEntityException.class,
+ () ->
+ TableMetaService.getInstance()
+ .updateTable(updatedTable.nameIdentifier(), updater2));
+ Assertions.assertTrue(e.getMessage().contains(newSchemaName));
+
+ // test update table with changing schema name to an existing schema
+ SchemaEntity newSchema =
+ createSchemaEntity(
+ RandomIdGenerator.INSTANCE.nextId(),
+ Namespace.of(metalakeName, catalogName),
+ newSchemaName,
+ auditInfo);
+ backend.insert(newSchema, false);
+ TableMetaService.getInstance().updateTable(updatedTable.nameIdentifier(),
updater2);
+
+ TableEntity retrievedTable2 =
+
TableMetaService.getInstance().getTableByIdentifier(updatedTable2.nameIdentifier());
+ Assertions.assertEquals(updatedTable2.id(), retrievedTable2.id());
+ Assertions.assertEquals(updatedTable2.name(), retrievedTable2.name());
+ Assertions.assertEquals(updatedTable2.namespace(),
retrievedTable2.namespace());
+ Assertions.assertEquals(updatedTable2.auditInfo(),
retrievedTable2.auditInfo());
+ compareTwoColumns(updatedTable2.columns(), retrievedTable2.columns());
+ }
+
+ private void compareTwoColumns(
+ List<ColumnEntity> expectedColumns, List<ColumnEntity> actualColumns) {
+ Assertions.assertEquals(expectedColumns.size(), actualColumns.size());
+ Map<String, ColumnEntity> expectedColumnsMap =
+ expectedColumns.stream().collect(Collectors.toMap(ColumnEntity::name,
Function.identity()));
+ actualColumns.forEach(
+ column -> {
+ ColumnEntity expectedColumn = expectedColumnsMap.get(column.name());
+ Assertions.assertNotNull(expectedColumn);
+ Assertions.assertEquals(expectedColumn.id(), column.id());
+ Assertions.assertEquals(expectedColumn.name(), column.name());
+ Assertions.assertEquals(expectedColumn.position(),
column.position());
+ Assertions.assertEquals(expectedColumn.comment(), column.comment());
+ Assertions.assertEquals(expectedColumn.dataType(),
column.dataType());
+ Assertions.assertEquals(expectedColumn.nullable(),
column.nullable());
+ Assertions.assertEquals(expectedColumn.autoIncrement(),
column.autoIncrement());
+ Assertions.assertEquals(expectedColumn.defaultValue(),
column.defaultValue());
+ Assertions.assertEquals(expectedColumn.auditInfo(),
column.auditInfo());
+ });
+ }
+}
diff --git
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
index 9d72d31fa9..a2d8a259da 100644
---
a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
+++
b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java
@@ -694,7 +694,8 @@ public class TestPOConverters {
TablePO.Builder builder =
TablePO.builder().withMetalakeId(1L).withCatalogId(1L).withSchemaId(1L);
TablePO initPO = POConverters.initializeTablePOWithVersion(tableEntity,
builder);
- TablePO updatePO = POConverters.updateTablePOWithVersion(initPO,
updatedTable, false);
+ TablePO updatePO =
+ POConverters.updateTablePOWithVersionAndSchemaId(initPO, updatedTable,
false, 1L);
assertEquals(1, initPO.getCurrentVersion());
assertEquals(1, initPO.getLastVersion());
assertEquals(0, initPO.getDeletedAt());
diff --git a/docs/manage-relational-metadata-using-gravitino.md
b/docs/manage-relational-metadata-using-gravitino.md
index 224d9a2500..a05d9393f1 100644
--- a/docs/manage-relational-metadata-using-gravitino.md
+++ b/docs/manage-relational-metadata-using-gravitino.md
@@ -1118,20 +1118,20 @@ Table t =
tableCatalog.alterTable(NameIdentifier.of("schema", "table"),
Currently, Gravitino supports the following changes to a table:
-| Supported modification | JSON
| Java
|
-|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------|
-| Rename table |
`{"@type":"rename","newName":"table_renamed"}`
| `TableChange.rename("table_renamed")` |
-| Update comment |
`{"@type":"updateComment","newComment":"new_comment"}`
| `TableChange.updateComment("new_comment")` |
-| Set a table property |
`{"@type":"setProperty","property":"key1","value":"value1"}`
| `TableChange.setProperty("key1", "value1")` |
-| Remove a table property |
`{"@type":"removeProperty","property":"key1"}`
| `TableChange.removeProperty("key1")` |
-| Add a column |
`{"@type":"addColumn","fieldName":["position"],"type":"varchar(20)","comment":"Position
of user","position":"FIRST","nullable": true, "autoIncrement": false,
"defaultValue" : {"type": "literal", "dataType": "varchar(20)", "value":
"Default Position"}}` | `TableChange.addColumn(...)` |
-| Delete a column | `{"@type":"deleteColumn","fieldName":
["name"], "ifExists": true}`
|
`TableChange.deleteColumn(...)` |
-| Rename a column |
`{"@type":"renameColumn","oldFieldName":["name_old"],
"newFieldName":"name_new"}`
| `TableChange.renameColumn(...)`
|
-| Update the column comment | `{"@type":"updateColumnComment",
"fieldName": ["name"], "newComment": "new comment"}`
|
`TableChange.updateColumnCommment(...)` |
-| Update the type of a column |
`{"@type":"updateColumnType","fieldName": ["name"], "newType":"varchar(100)"}`
| `TableChange.updateColumnType(...)` |
-| Update the nullability of a column |
`{"@type":"updateColumnNullability","fieldName": ["name"],"nullable":true}`
| `TableChange.updateColumnNullability(...)` |
-| Update the position of a column |
`{"@type":"updateColumnPosition","fieldName": ["name"],
"newPosition":"default"}`
| `TableChange.updateColumnPosition(...)`
|
-| Update the default value of a column |
`{"@type":"updateColumnDefaultValue","fieldName": ["name"],
"newDefaultValue":{"type":"literal","dataType":"varchar(100)","value":"new
default value}}`
|
`TableChange.updateColumnDefaultValue(...)` |
+| Supported modification | JSON
| Java
|
+|--------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
+| Rename table |
`{"@type":"rename","newName":"table_renamed"}` or
`{"@type":"rename","newName":"table_renamed, "newSchemaName":"new_schema"}`
|
`TableChange.rename("table_renamed")` or `TableChange.rename("table_renamed",
"new_schema")` |
+| Update comment |
`{"@type":"updateComment","newComment":"new_comment"}`
| `TableChange.updateComment("new_comment")`
|
+| Set a table property |
`{"@type":"setProperty","property":"key1","value":"value1"}`
| `TableChange.setProperty("key1", "value1")`
|
+| Remove a table property |
`{"@type":"removeProperty","property":"key1"}`
| `TableChange.removeProperty("key1")`
|
+| Add a column |
`{"@type":"addColumn","fieldName":["position"],"type":"varchar(20)","comment":"Position
of user","position":"FIRST","nullable": true, "autoIncrement": false,
"defaultValue" : {"type": "literal", "dataType": "varchar(20)", "value":
"Default Position"}}` | `TableChange.addColumn(...)`
|
+| Delete a column | `{"@type":"deleteColumn","fieldName":
["name"], "ifExists": true}`
|
`TableChange.deleteColumn(...)`
|
+| Rename a column |
`{"@type":"renameColumn","oldFieldName":["name_old"],
"newFieldName":"name_new"}`
| `TableChange.renameColumn(...)`
|
+| Update the column comment | `{"@type":"updateColumnComment",
"fieldName": ["name"], "newComment": "new comment"}`
|
`TableChange.updateColumnCommment(...)`
|
+| Update the type of a column |
`{"@type":"updateColumnType","fieldName": ["name"], "newType":"varchar(100)"}`
| `TableChange.updateColumnType(...)`
|
+| Update the nullability of a column |
`{"@type":"updateColumnNullability","fieldName": ["name"],"nullable":true}`
| `TableChange.updateColumnNullability(...)`
|
+| Update the position of a column |
`{"@type":"updateColumnPosition","fieldName": ["name"],
"newPosition":"default"}`
| `TableChange.updateColumnPosition(...)`
|
+| Update the default value of a column |
`{"@type":"updateColumnDefaultValue","fieldName": ["name"],
"newDefaultValue":{"type":"literal","dataType":"varchar(100)","value":"new
default value}}`
|
`TableChange.updateColumnDefaultValue(...)`
|
### Drop a table
diff --git a/docs/open-api/tables.yaml b/docs/open-api/tables.yaml
index 361602e1b5..1e199c5dd1 100644
--- a/docs/open-api/tables.yaml
+++ b/docs/open-api/tables.yaml
@@ -431,9 +431,14 @@ components:
newName:
type: string
description: The new name of the table
+ newSchemaName:
+ type: string
+ description: The new schema name of the table, if not provided, the
schema will not be changed
+ nullable: true
example: {
"@type": "rename",
- "newName": "my_hive_table_new"
+ "newName": "my_hive_table_new",
+ "newSchemaName": "my_hive_schema_new"
}
UpdateTableCommentRequest: