diff --git a/core/trino-main/src/main/java/io/trino/execution/RenameColumnTask.java b/core/trino-main/src/main/java/io/trino/execution/RenameColumnTask.java index 82ba7f92f0a3..adfe0765fc23 100644 --- a/core/trino-main/src/main/java/io/trino/execution/RenameColumnTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/RenameColumnTask.java @@ -22,15 +22,23 @@ import io.trino.metadata.RedirectionAwareTableHandle; import io.trino.metadata.TableHandle; import io.trino.security.AccessControl; +import io.trino.spi.TrinoException; import io.trino.spi.connector.ColumnHandle; +import io.trino.spi.connector.ColumnMetadata; +import io.trino.spi.type.RowType; +import io.trino.spi.type.Type; import io.trino.sql.tree.Expression; import io.trino.sql.tree.RenameColumn; import java.util.List; import java.util.Map; +import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.Iterables.getLast; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.util.concurrent.Futures.immediateVoidFuture; import static io.trino.metadata.MetadataUtil.createQualifiedObjectName; +import static io.trino.spi.StandardErrorCode.AMBIGUOUS_NAME; import static io.trino.spi.StandardErrorCode.COLUMN_ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; @@ -76,11 +84,10 @@ public ListenableFuture<Void> execute( } TableHandle tableHandle = redirectionAwareTableHandle.getTableHandle().get(); - String source = statement.getSource().getValue().toLowerCase(ENGLISH); + String source = statement.getSource().getParts().get(0).toLowerCase(ENGLISH); String target = statement.getTarget().getValue().toLowerCase(ENGLISH); QualifiedObjectName qualifiedTableName = redirectionAwareTableHandle.getRedirectedTableName().orElse(originalTableName); - accessControl.checkCanRenameColumn(session.toSecurityContext(), qualifiedTableName); Map<String, ColumnHandle> columnHandles = metadata.getColumnHandles(session, tableHandle); ColumnHandle columnHandle = columnHandles.get(source); @@ -90,17 +97,72 @@ public ListenableFuture<Void> execute( } return immediateVoidFuture(); } - - if (columnHandles.containsKey(target)) { - throw semanticException(COLUMN_ALREADY_EXISTS, statement, "Column '%s' already exists", target); - } - if (metadata.getColumnMetadata(session, tableHandle, columnHandle).isHidden()) { throw semanticException(NOT_SUPPORTED, statement, "Cannot rename hidden column"); } - metadata.renameColumn(session, tableHandle, qualifiedTableName.asCatalogSchemaTableName(), columnHandle, target); + if (statement.getSource().getParts().size() == 1) { + accessControl.checkCanRenameColumn(session.toSecurityContext(), qualifiedTableName); + + if (columnHandles.containsKey(target)) { + throw semanticException(COLUMN_ALREADY_EXISTS, statement, "Column '%s' already exists", target); + } + + metadata.renameColumn(session, tableHandle, qualifiedTableName.asCatalogSchemaTableName(), columnHandle, target); + } + else { + accessControl.checkCanAlterColumn(session.toSecurityContext(), qualifiedTableName); + + List<String> fieldPath = statement.getSource().getParts(); + + ColumnMetadata columnMetadata = metadata.getColumnMetadata(session, tableHandle, columnHandle); + Type currentType = columnMetadata.getType(); + for (int i = 1; i < fieldPath.size() - 1; i++) { + String fieldName = fieldPath.get(i); + List<RowType.Field> candidates = getCandidates(currentType, fieldName); + + if (candidates.isEmpty()) { + throw semanticException(COLUMN_NOT_FOUND, statement, "Field '%s' does not exist within %s", fieldName, currentType); + } + if (candidates.size() > 1) { + throw semanticException(AMBIGUOUS_NAME, statement, "Field path %s within %s is ambiguous", fieldPath, columnMetadata.getType()); + } + currentType = getOnlyElement(candidates).getType(); + } + + String sourceFieldName = getLast(statement.getSource().getParts()); + List<RowType.Field> sourceCandidates = getCandidates(currentType, sourceFieldName); + if (sourceCandidates.isEmpty()) { + if (!statement.isColumnExists()) { + throw semanticException(COLUMN_NOT_FOUND, statement, "Field '%s' does not exist", source); + } + return immediateVoidFuture(); + } + if (sourceCandidates.size() > 1) { + throw semanticException(AMBIGUOUS_NAME, statement, "Field path %s within %s is ambiguous", fieldPath, columnMetadata.getType()); + } + + List<RowType.Field> targetCandidates = getCandidates(currentType, target); + if (!targetCandidates.isEmpty()) { + throw semanticException(COLUMN_ALREADY_EXISTS, statement, "Field '%s' already exists", target); + } + + metadata.renameField(session, tableHandle, fieldPath, target); + } return immediateVoidFuture(); } + + private static List<RowType.Field> getCandidates(Type type, String fieldName) + { + if (!(type instanceof RowType rowType)) { + throw new TrinoException(NOT_SUPPORTED, "Unsupported type: " + type); + } + List<RowType.Field> candidates = rowType.getFields().stream() + // case-insensitive match + .filter(rowField -> rowField.getName().isPresent() && rowField.getName().get().equalsIgnoreCase(fieldName)) + .collect(toImmutableList()); + + return candidates; + } } diff --git a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java index bc5e665bdd7c..004139f67f8f 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java @@ -237,6 +237,11 @@ Optional<TableExecuteHandle> getTableHandleForExecute( */ void renameColumn(Session session, TableHandle tableHandle, CatalogSchemaTableName table, ColumnHandle source, String target); + /** + * Rename the specified field. + */ + void renameField(Session session, TableHandle tableHandle, List<String> fieldPath, String target); + /** * Add the specified column to the table. */ diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index 04bf6a575945..584399c2d98a 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -757,6 +757,14 @@ public void renameColumn(Session session, TableHandle tableHandle, CatalogSchema } } + @Override + public void renameField(Session session, TableHandle tableHandle, List<String> fieldPath, String target) + { + CatalogHandle catalogHandle = tableHandle.getCatalogHandle(); + ConnectorMetadata metadata = getMetadataForWrite(session, catalogHandle); + metadata.renameField(session.toConnectorSession(catalogHandle), tableHandle.getConnectorHandle(), fieldPath, target.toLowerCase(ENGLISH)); + } + @Override public void addColumn(Session session, TableHandle tableHandle, CatalogSchemaTableName table, ColumnMetadata column) { diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java index 36c898f38f8a..b56338301f90 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingConnectorMetadata.java @@ -477,6 +477,15 @@ public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHan } } + @Override + public void renameField(ConnectorSession session, ConnectorTableHandle tableHandle, List<String> fieldPath, String target) + { + Span span = startSpan("renameField", tableHandle); + try (var ignored = scopedSpan(span)) { + delegate.renameField(session, tableHandle, fieldPath, target); + } + } + @Override public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column) { diff --git a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java index 8eea476e1e64..8a4c64cc4afd 100644 --- a/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java +++ b/core/trino-main/src/main/java/io/trino/tracing/TracingMetadata.java @@ -440,6 +440,15 @@ public void renameColumn(Session session, TableHandle tableHandle, CatalogSchema } } + @Override + public void renameField(Session session, TableHandle tableHandle, List<String> fieldPath, String target) + { + Span span = startSpan("renameField", tableHandle); + try (var ignored = scopedSpan(span)) { + delegate.renameField(session, tableHandle, fieldPath, target); + } + } + @Override public void addColumn(Session session, TableHandle tableHandle, CatalogSchemaTableName table, ColumnMetadata column) { diff --git a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java index d27ef50b619e..d37384791b71 100644 --- a/core/trino-main/src/test/java/io/trino/connector/MockConnector.java +++ b/core/trino-main/src/test/java/io/trino/connector/MockConnector.java @@ -602,6 +602,12 @@ public void setTableAuthorization(ConnectorSession session, SchemaTableName tabl @Override public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle source, String target) {} + @Override + public void renameField(ConnectorSession session, ConnectorTableHandle tableHandle, List<String> fieldPath, String target) + { + throw new UnsupportedOperationException(); + } + @Override public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column) {} diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameColumnTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameColumnTask.java index 6a485e257fdc..48e3b70c02ba 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameColumnTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameColumnTask.java @@ -21,16 +21,22 @@ import io.trino.security.AllowAllAccessControl; import io.trino.spi.connector.ColumnMetadata; import io.trino.spi.connector.ConnectorTableMetadata; +import io.trino.spi.type.RowType; import io.trino.sql.tree.Identifier; import io.trino.sql.tree.NodeLocation; import io.trino.sql.tree.QualifiedName; import io.trino.sql.tree.RenameColumn; import org.testng.annotations.Test; +import java.util.Optional; + import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.spi.StandardErrorCode.AMBIGUOUS_NAME; +import static io.trino.spi.StandardErrorCode.COLUMN_ALREADY_EXISTS; import static io.trino.spi.StandardErrorCode.COLUMN_NOT_FOUND; import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; import static io.trino.spi.type.BigintType.BIGINT; +import static io.trino.spi.type.RowType.rowType; import static io.trino.sql.QueryUtil.identifier; import static io.trino.testing.TestingHandles.TEST_CATALOG_NAME; import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; @@ -49,7 +55,7 @@ public void testRenameColumn() assertThat(metadata.getTableMetadata(testSession, table).getColumns()) .containsExactly(new ColumnMetadata("a", BIGINT), new ColumnMetadata("b", BIGINT)); - getFutureValue(executeRenameColumn(asQualifiedName(tableName), identifier("a"), identifier("a_renamed"), false, false)); + getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("a"), identifier("a_renamed"), false, false)); assertThat(metadata.getTableMetadata(testSession, table).getColumns()) .containsExactly(new ColumnMetadata("a_renamed", BIGINT), new ColumnMetadata("b", BIGINT)); } @@ -59,7 +65,7 @@ public void testRenameColumnNotExistingTable() { QualifiedObjectName tableName = qualifiedObjectName("not_existing_table"); - assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), identifier("a"), identifier("a_renamed"), false, false))) + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("a"), identifier("a_renamed"), false, false))) .hasErrorCode(TABLE_NOT_FOUND) .hasMessageContaining("Table '%s' does not exist", tableName); } @@ -69,7 +75,7 @@ public void testRenameColumnNotExistingTableIfExists() { QualifiedName tableName = qualifiedName("not_existing_table"); - getFutureValue(executeRenameColumn(tableName, identifier("a"), identifier("a_renamed"), true, false)); + getFutureValue(executeRenameColumn(tableName, QualifiedName.of("a"), identifier("a_renamed"), true, false)); // no exception } @@ -79,7 +85,7 @@ public void testRenameMissingColumn() QualifiedObjectName tableName = qualifiedObjectName("existing_table"); metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), false); - assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), identifier("missing_column"), identifier("test"), false, false))) + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("missing_column"), identifier("test"), false, false))) .hasErrorCode(COLUMN_NOT_FOUND) .hasMessageContaining("Column 'missing_column' does not exist"); } @@ -91,7 +97,7 @@ public void testRenameColumnIfExists() metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), false); TableHandle table = metadata.getTableHandle(testSession, tableName).get(); - getFutureValue(executeRenameColumn(asQualifiedName(tableName), identifier("missing_column"), identifier("test"), false, true)); + getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("missing_column"), identifier("test"), false, true)); assertThat(metadata.getTableMetadata(testSession, table).getColumns()) .containsExactly(new ColumnMetadata("a", BIGINT), new ColumnMetadata("b", BIGINT)); } @@ -102,7 +108,7 @@ public void testRenameColumnOnView() QualifiedObjectName viewName = qualifiedObjectName("existing_view"); metadata.createView(testSession, viewName, someView(), false); - assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(viewName), identifier("a"), identifier("a_renamed"), false, false))) + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(viewName), QualifiedName.of("a"), identifier("a_renamed"), false, false))) .hasErrorCode(TABLE_NOT_FOUND) .hasMessageContaining("Table '%s' does not exist", viewName); } @@ -113,12 +119,106 @@ public void testRenameColumnOnMaterializedView() QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); metadata.createMaterializedView(testSession, QualifiedObjectName.valueOf(materializedViewName.toString()), someMaterializedView(), false, false); - assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(materializedViewName), identifier("a"), identifier("a_renamed"), false, false))) + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(materializedViewName), QualifiedName.of("a"), identifier("a_renamed"), false, false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist", materializedViewName); + } + + @Test + public void testRenameFieldNotExistingTable() + { + QualifiedObjectName tableName = qualifiedObjectName("not_existing_table"); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("test"), identifier("x"), false, false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist", tableName); + } + + @Test + public void testRenameFieldNotExistingTableIfExists() + { + QualifiedName tableName = qualifiedName("not_existing_table"); + + getFutureValue(executeRenameColumn(tableName, QualifiedName.of("test"), identifier("x"), true, false)); + // no exception + } + + @Test + public void testRenameMissingField() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("missing_column"), identifier("x"), false, false))) + .hasErrorCode(COLUMN_NOT_FOUND) + .hasMessageContaining("Column 'missing_column' does not exist"); + } + + @Test + public void testRenameFieldIfExists() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, simpleTable(tableName), false); + TableHandle table = metadata.getTableHandle(testSession, tableName).get(); + + getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("c"), identifier("x"), false, true)); + assertThat(metadata.getTableMetadata(testSession, table).getColumns()) + .containsExactly(new ColumnMetadata("a", BIGINT), new ColumnMetadata("b", BIGINT)); + } + + @Test + public void testUnsupportedRenameDuplicatedField() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, rowTable(tableName, new RowType.Field(Optional.of("a"), BIGINT), new RowType.Field(Optional.of("a"), BIGINT)), false); + TableHandle table = metadata.getTableHandle(testSession, tableName).get(); + assertThat(metadata.getTableMetadata(testSession, table).getColumns()) + .isEqualTo(ImmutableList.of(new ColumnMetadata("col", RowType.rowType( + new RowType.Field(Optional.of("a"), BIGINT), new RowType.Field(Optional.of("a"), BIGINT))))); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("col", "a"), identifier("x"), false, false))) + .hasErrorCode(AMBIGUOUS_NAME) + .hasMessageContaining("Field path [col, a] within row(a bigint, a bigint) is ambiguous"); + } + + @Test + public void testUnsupportedRenameToExistingField() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, TEST_CATALOG_NAME, rowTable(tableName, new RowType.Field(Optional.of("a"), BIGINT), new RowType.Field(Optional.of("b"), BIGINT)), false); + TableHandle table = metadata.getTableHandle(testSession, tableName).get(); + assertThat(metadata.getTableMetadata(testSession, table).getColumns()) + .isEqualTo(ImmutableList.of(new ColumnMetadata("col", RowType.rowType( + new RowType.Field(Optional.of("a"), BIGINT), new RowType.Field(Optional.of("b"), BIGINT))))); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(tableName), QualifiedName.of("col", "a"), identifier("b"), false, false))) + .hasErrorCode(COLUMN_ALREADY_EXISTS) + .hasMessageContaining("Field 'b' already exists"); + } + + @Test + public void testRenameFieldOnView() + { + QualifiedObjectName viewName = qualifiedObjectName("existing_view"); + metadata.createView(testSession, viewName, someView(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(viewName), QualifiedName.of("test"), identifier("x"), false, false))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessageContaining("Table '%s' does not exist", viewName); + } + + @Test + public void testRenameFieldOnMaterializedView() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + metadata.createMaterializedView(testSession, QualifiedObjectName.valueOf(materializedViewName.toString()), someMaterializedView(), false, false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameColumn(asQualifiedName(materializedViewName), QualifiedName.of("test"), identifier("x"), false, false))) .hasErrorCode(TABLE_NOT_FOUND) .hasMessageContaining("Table '%s' does not exist", materializedViewName); } - private ListenableFuture<Void> executeRenameColumn(QualifiedName table, Identifier source, Identifier target, boolean tableExists, boolean columnExists) + private ListenableFuture<Void> executeRenameColumn(QualifiedName table, QualifiedName source, Identifier target, boolean tableExists, boolean columnExists) { return new RenameColumnTask(plannerContext.getMetadata(), new AllowAllAccessControl()) .execute(new RenameColumn(new NodeLocation(1, 1), table, source, target, tableExists, columnExists), queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); @@ -128,4 +228,10 @@ private static ConnectorTableMetadata simpleTable(QualifiedObjectName tableName) { return new ConnectorTableMetadata(tableName.asSchemaTableName(), ImmutableList.of(new ColumnMetadata("a", BIGINT), new ColumnMetadata("b", BIGINT))); } + + private static ConnectorTableMetadata rowTable(QualifiedObjectName tableName, RowType.Field... fields) + { + return new ConnectorTableMetadata(tableName.asSchemaTableName(), ImmutableList.of( + new ColumnMetadata("col", rowType(fields)))); + } } diff --git a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java index 3a6fd8c66a1c..a38dda8b79ad 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java @@ -306,6 +306,12 @@ public void renameColumn(Session session, TableHandle tableHandle, CatalogSchema throw new UnsupportedOperationException(); } + @Override + public void renameField(Session session, TableHandle tableHandle, List<String> fieldPath, String target) + { + throw new UnsupportedOperationException(); + } + @Override public void addColumn(Session session, TableHandle tableHandle, CatalogSchemaTableName table, ColumnMetadata column) { diff --git a/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java index 36a37de6b575..86c34c265116 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/CountingAccessMetadata.java @@ -316,6 +316,12 @@ public void renameColumn(Session session, TableHandle tableHandle, CatalogSchema delegate.renameColumn(session, tableHandle, table, source, target); } + @Override + public void renameField(Session session, TableHandle tableHandle, List<String> fieldPath, String target) + { + throw new UnsupportedOperationException(); + } + @Override public void addColumn(Session session, TableHandle tableHandle, CatalogSchemaTableName table, ColumnMetadata column) { diff --git a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 index 8dd7e011f18c..bd4023c0a3e8 100644 --- a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 +++ b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 @@ -76,7 +76,7 @@ statement | ALTER TABLE (IF EXISTS)? tableName=qualifiedName ADD COLUMN (IF NOT EXISTS)? column=columnDefinition #addColumn | ALTER TABLE (IF EXISTS)? tableName=qualifiedName - RENAME COLUMN (IF EXISTS)? from=identifier TO to=identifier #renameColumn + RENAME COLUMN (IF EXISTS)? from=qualifiedName TO to=identifier #renameColumn | ALTER TABLE (IF EXISTS)? tableName=qualifiedName DROP COLUMN (IF EXISTS)? column=qualifiedName #dropColumn | ALTER TABLE (IF EXISTS)? tableName=qualifiedName diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index b6dd454b039a..25e491e065c9 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -766,7 +766,7 @@ public Node visitRenameColumn(SqlBaseParser.RenameColumnContext context) return new RenameColumn( getLocation(context), getQualifiedName(context.tableName), - (Identifier) visit(context.from), + getQualifiedName(context.from), (Identifier) visit(context.to), context.EXISTS().stream().anyMatch(node -> node.getSymbol().getTokenIndex() < context.COLUMN().getSymbol().getTokenIndex()), context.EXISTS().stream().anyMatch(node -> node.getSymbol().getTokenIndex() > context.COLUMN().getSymbol().getTokenIndex())); diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/RenameColumn.java b/core/trino-parser/src/main/java/io/trino/sql/tree/RenameColumn.java index 2edbc4fdac78..e6f9283569fc 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/RenameColumn.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/RenameColumn.java @@ -26,12 +26,12 @@ public class RenameColumn extends Statement { private final QualifiedName table; - private final Identifier source; + private final QualifiedName source; private final Identifier target; private final boolean tableExists; private final boolean columnExists; - public RenameColumn(NodeLocation location, QualifiedName table, Identifier source, Identifier target, boolean tableExists, boolean columnExists) + public RenameColumn(NodeLocation location, QualifiedName table, QualifiedName source, Identifier target, boolean tableExists, boolean columnExists) { super(Optional.of(location)); this.table = requireNonNull(table, "table is null"); @@ -46,7 +46,7 @@ public QualifiedName getTable() return table; } - public Identifier getSource() + public QualifiedName getSource() { return source; } diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java index 464c1a1db003..a5a47bff89e9 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java @@ -2924,7 +2924,7 @@ public void testRenameColumn() QualifiedName.of(ImmutableList.of( new Identifier(location(1, 13), "foo", false), new Identifier(location(1, 17), "t", false))), - new Identifier(location(1, 33), "a", false), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 33), "a", false))), new Identifier(location(1, 38), "b", false), false, false)); @@ -2935,7 +2935,7 @@ public void testRenameColumn() QualifiedName.of(ImmutableList.of( new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), - new Identifier(location(1, 43), "a", false), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "a", false))), new Identifier(location(1, 48), "b", false), true, false)); @@ -2946,7 +2946,7 @@ public void testRenameColumn() QualifiedName.of(ImmutableList.of( new Identifier(location(1, 13), "foo", false), new Identifier(location(1, 17), "t", false))), - new Identifier(location(1, 43), "a", false), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "a", false))), new Identifier(location(1, 48), "b", false), false, true)); @@ -2957,10 +2957,64 @@ public void testRenameColumn() QualifiedName.of(ImmutableList.of( new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), - new Identifier(location(1, 53), "a", false), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 53), "a", false))), new Identifier(location(1, 58), "b", false), true, true)); + + assertThat(statement("ALTER TABLE foo.t RENAME COLUMN c.d TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 13), "foo", false), new Identifier(location(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 33), "c", false), new Identifier(location(1, 35), "d", false))), + new Identifier(location(1, 40), "x", false), + false, + false)); + + assertThat(statement("ALTER TABLE foo.t RENAME COLUMN IF EXISTS c.d TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 13), "foo", false), new Identifier(location(1, 17), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "c", false), new Identifier(location(1, 45), "d", false))), + new Identifier(location(1, 50), "x", false), + false, + true)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t RENAME COLUMN c.d TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "c", false), new Identifier(location(1, 45), "d", false))), + new Identifier(location(1, 50), "x", false), + true, + false)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t RENAME COLUMN b.\"c.d\" TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "b", false), new Identifier(location(1, 45), "c.d", true))), + new Identifier(location(1, 54), "x", false), + true, + false)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t RENAME COLUMN \"b.c\".d TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 43), "b.c", true), new Identifier(location(1, 49), "d", false))), + new Identifier(location(1, 54), "x", false), + true, + false)); + + assertThat(statement("ALTER TABLE IF EXISTS foo.t RENAME COLUMN IF EXISTS c.d TO x")) + .isEqualTo(new RenameColumn( + location(1, 1), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 23), "foo", false), new Identifier(location(1, 27), "t", false))), + QualifiedName.of(ImmutableList.of(new Identifier(location(1, 53), "c", false), new Identifier(location(1, 55), "d", false))), + new Identifier(location(1, 60), "x", false), + true, + true)); } @Test diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index 0968e8af6f7d..730af0bbc234 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -486,6 +486,18 @@ default void renameColumn(ConnectorSession session, ConnectorTableHandle tableHa throw new TrinoException(NOT_SUPPORTED, "This connector does not support renaming columns"); } + /** + * Rename the specified field, potentially nested, to a row. + * + * @param fieldPath path starting with column name. + * @param target the new field name. The field position and nested level shouldn't be changed. + */ + @Experimental(eta = "2023-09-01") // TODO add support for rows inside arrays and maps and for anonymous row fields + default void renameField(ConnectorSession session, ConnectorTableHandle tableHandle, List<String> fieldPath, String target) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support renaming fields"); + } + /** * Drop the specified column */ diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java index 232110ad65bb..19f452c80834 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java @@ -423,6 +423,14 @@ public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHan } } + @Override + public void renameField(ConnectorSession session, ConnectorTableHandle tableHandle, List<String> fieldPath, String target) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.renameField(session, tableHandle, fieldPath, target); + } + } + @Override public void dropColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle column) {