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)
     {