You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@paimon.apache.org by "s7monk (via GitHub)" <gi...@apache.org> on 2023/10/10 19:00:15 UTC

[PR] [Refactor] Refactor server module [incubator-paimon-webui]

s7monk opened a new pull request, #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48

   <!-- Please specify the module before the PR name: [core] ... or [flink] ... -->
   
   ### Purpose
   
   Refactor server module.
   
   ### Tests
   
   <!-- List UT and IT cases to verify this change -->
   
   ### API and Format
   
   <!-- Does this change affect API or storage format -->
   
   ### Documentation
   
   <!-- Does this change introduce a new feature -->
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [Improvement] Refactor server module [incubator-paimon-webui]

Posted by "SteNicholas (via GitHub)" <gi...@apache.org>.
SteNicholas commented on code in PR #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48#discussion_r1358035333


##########
paimon-web-ui-new/auto-imports.d.ts:
##########
@@ -1,20 +1,3 @@
-/* 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. */
-

Review Comment:
   Why update this file? Pls revert.



##########
paimon-web-server/src/main/resources/application-dev-mysql.yml:
##########
@@ -0,0 +1,21 @@
+# 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.
+
+spring:
+  datasource:
+    url: jdbc:mysql://${MYSQL_ADDR:43.143.237.199:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

Review Comment:
   @s7monk, pls do not expose the IP in any files.



##########
paimon-web-server/src/main/resources/i18n/messages.properties:
##########
@@ -35,7 +35,16 @@ menu.name.exist=This menu name is exist:{0}
 menu.path.invalid=This menu path is invalid:{0}
 catalog.name.exist=This catalog name {0} is exist
 catalog.create.error=An exception is returned when calling the Paimon API to create a Catalog.
+catalog.remove.error=An exception is returned when calling the Paimon API to remove a Catalog.

Review Comment:
   ```suggestion
   catalog.remove.error=Exception calling Paimon Catalog API to remove a Catalog.
   ```
   The below could update with this suggestion.



##########
pom.xml:
##########
@@ -358,6 +358,10 @@ under the License.
                         <!-- artifacts created during release process -->
                         <exclude>release/**</exclude>
                         <exclude>paimon-web-ui/node_modules/**</exclude>
+                        <exclude>/node_modules/**</exclude>

Review Comment:
   Why update this file? Pls revert.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [Improvement] Refactor server module [incubator-paimon-webui]

Posted by "SteNicholas (via GitHub)" <gi...@apache.org>.
SteNicholas commented on code in PR #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48#discussion_r1354142370


##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -45,46 +48,42 @@ public class CatalogController {
     @Autowired private CatalogService catalogService;
 
     /**
-     * Create a filesystem catalog.
+     * Create a catalog.
      *
-     * @param catalogInfo The catalogInfo for the filesystem catalog.
+     * @param catalogInfo The catalogInfo for the catalog.
      * @return The created catalog.
      */
-    @PostMapping("/createFilesystemCatalog")
-    public R<Void> createFilesystemCatalog(@RequestBody CatalogInfo catalogInfo) {
+    @PostMapping("/createCatalog")
+    public R<Void> createCatalog(@RequestBody CatalogInfo catalogInfo) {
         if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
             return R.failed(Status.CATALOG_NAME_IS_EXIST, catalogInfo.getCatalogName());
         }
 
         try {
-            CatalogCreator.createFilesystemCatalog(catalogInfo.getWarehouse());
+            if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.FILESYSTEM.getMode())) {
+                PaimonServiceFactory.createFileSystemCatalogService(
+                        catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+            } else if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.HIVE.getMode())) {
+                if (StringUtils.isNotBlank(catalogInfo.getHiveConfDir())) {
+                    PaimonServiceFactory.createHiveCatalogService(
+                            catalogInfo.getCatalogName(),
+                            catalogInfo.getWarehouse(),
+                            catalogInfo.getHiveUri(),
+                            catalogInfo.getHiveConfDir());
+                } else {
+                    PaimonServiceFactory.createHiveCatalogService(
+                            catalogInfo.getCatalogName(),
+                            catalogInfo.getWarehouse(),
+                            catalogInfo.getHiveUri(),
+                            null);
+                }
+            } else {
+                PaimonServiceFactory.createFileSystemCatalogService(

Review Comment:
   Could this branch merge with `if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.FILESYSTEM.getMode()))`?



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -45,46 +48,42 @@ public class CatalogController {
     @Autowired private CatalogService catalogService;
 
     /**
-     * Create a filesystem catalog.
+     * Create a catalog.
      *
-     * @param catalogInfo The catalogInfo for the filesystem catalog.
+     * @param catalogInfo The catalogInfo for the catalog.
      * @return The created catalog.
      */
-    @PostMapping("/createFilesystemCatalog")
-    public R<Void> createFilesystemCatalog(@RequestBody CatalogInfo catalogInfo) {
+    @PostMapping("/createCatalog")
+    public R<Void> createCatalog(@RequestBody CatalogInfo catalogInfo) {
         if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
             return R.failed(Status.CATALOG_NAME_IS_EXIST, catalogInfo.getCatalogName());
         }
 
         try {
-            CatalogCreator.createFilesystemCatalog(catalogInfo.getWarehouse());
+            if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.FILESYSTEM.getMode())) {
+                PaimonServiceFactory.createFileSystemCatalogService(
+                        catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+            } else if (catalogInfo.getCatalogType().equalsIgnoreCase(CatalogMode.HIVE.getMode())) {
+                if (StringUtils.isNotBlank(catalogInfo.getHiveConfDir())) {
+                    PaimonServiceFactory.createHiveCatalogService(
+                            catalogInfo.getCatalogName(),
+                            catalogInfo.getWarehouse(),
+                            catalogInfo.getHiveUri(),
+                            catalogInfo.getHiveConfDir());
+                } else {
+                    PaimonServiceFactory.createHiveCatalogService(
+                            catalogInfo.getCatalogName(),
+                            catalogInfo.getWarehouse(),
+                            catalogInfo.getHiveUri(),
+                            null);
+                }
+            } else {
+                PaimonServiceFactory.createFileSystemCatalogService(
+                        catalogInfo.getCatalogName(), catalogInfo.getWarehouse());
+            }
             return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
         } catch (Exception e) {
-            e.printStackTrace();
-            return R.failed(Status.CATALOG_CREATE_ERROR);
-        }
-    }
-
-    /**
-     * Create a hive catalog.
-     *
-     * @param catalogInfo The information for the hive catalog.
-     * @return The created catalog.
-     */
-    @PostMapping("/createHiveCatalog")
-    public R<Void> createHiveCatalog(@RequestBody CatalogInfo catalogInfo) {
-        if (!catalogService.checkCatalogNameUnique(catalogInfo)) {
-            return R.failed(Status.CATALOG_NAME_IS_EXIST, catalogInfo.getCatalogName());
-        }
-
-        try {
-            CatalogCreator.createHiveCatalog(
-                    catalogInfo.getWarehouse(),
-                    catalogInfo.getHiveUri(),
-                    catalogInfo.getHiveConfDir());
-            return catalogService.save(catalogInfo) ? R.succeed() : R.failed();
-        } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating catalog.", e);

Review Comment:
   ```suggestion
               log.error("Exception with creating catalog.", e);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -101,13 +100,15 @@ public R<List<CatalogInfo>> getCatalog() {
     }
 
     /**
-     * Removes a catalog by its ID.
+     * Removes a catalog based on the catalog name.

Review Comment:
   ```suggestion
        * Removes a catalog with given catalog name.
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -101,13 +100,15 @@ public R<List<CatalogInfo>> getCatalog() {
     }
 
     /**
-     * Removes a catalog by its ID.
+     * Removes a catalog based on the catalog name.
      *
-     * @param catalogId The ID of the catalog to be removed.
-     * @return A response indicating the success or failure of the removal operation.
+     * @param catalogName The catalog name.
+     * @return R<Void/> indicating the success or failure of the operation.
      */
-    @DeleteMapping("/{catalogId}")
-    public R<Void> remove(@PathVariable Integer catalogId) {
-        return catalogService.removeById(catalogId) ? R.succeed() : R.failed();
+    @DeleteMapping("/removeCatalog/{catalogName}")
+    public R<Void> remove(@PathVariable String catalogName) {
+        QueryWrapper<CatalogInfo> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("catalog_name", catalogName);
+        return catalogService.remove(queryWrapper) ? R.succeed() : R.failed();

Review Comment:
   ```suggestion
           return catalogService.remove(queryWrapper) ? R.succeed() : R.failed(Status.CATALOG_REMOVE_ERROR);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -57,15 +58,15 @@ public class DatabaseController {
     @PostMapping("/createDatabase")
     public R<Void> createDatabase(@RequestBody DatabaseInfo databaseInfo) {
         try {
-            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
-            Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
-            if (DatabaseManager.databaseExists(catalog, databaseInfo.getDatabaseName())) {
+            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo.getCatalogName());
+            PaimonService service = CatalogUtils.getPaimonService(catalogInfo);

Review Comment:
   `CatalogUtils` should rename to `ServiceUtils`.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -101,13 +100,15 @@ public R<List<CatalogInfo>> getCatalog() {
     }
 
     /**
-     * Removes a catalog by its ID.
+     * Removes a catalog based on the catalog name.
      *
-     * @param catalogId The ID of the catalog to be removed.
-     * @return A response indicating the success or failure of the removal operation.
+     * @param catalogName The catalog name.
+     * @return R<Void/> indicating the success or failure of the operation.
      */
-    @DeleteMapping("/{catalogId}")
-    public R<Void> remove(@PathVariable Integer catalogId) {
-        return catalogService.removeById(catalogId) ? R.succeed() : R.failed();
+    @DeleteMapping("/removeCatalog/{catalogName}")
+    public R<Void> remove(@PathVariable String catalogName) {

Review Comment:
   ```suggestion
       public R<Void> removeCatalog(@PathVariable String catalogName) {
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -101,13 +100,15 @@ public R<List<CatalogInfo>> getCatalog() {
     }
 
     /**
-     * Removes a catalog by its ID.
+     * Removes a catalog based on the catalog name.
      *
-     * @param catalogId The ID of the catalog to be removed.
-     * @return A response indicating the success or failure of the removal operation.
+     * @param catalogName The catalog name.
+     * @return R<Void/> indicating the success or failure of the operation.
      */
-    @DeleteMapping("/{catalogId}")
-    public R<Void> remove(@PathVariable Integer catalogId) {
-        return catalogService.removeById(catalogId) ? R.succeed() : R.failed();
+    @DeleteMapping("/removeCatalog/{catalogName}")

Review Comment:
   ```suggestion
       @DeleteMapping("/remove/{catalogName}")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -99,34 +101,35 @@ public R<List<DatabaseInfo>> getAllDatabases() {
         return R.succeed(databaseInfoList);
     }
 
-    /**
-     * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
-     *
-     * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
-     * @return The associated CatalogInfo object, or null if it doesn't exist.
-     */
-    private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
-        LambdaQueryWrapper<CatalogInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
-        return catalogService.getOne(queryWrapper);
-    }
-
     /**
      * Removes a database by its name.
      *
-     * @param databaseInfo The information of the database to be removed.
+     * @param databaseName The database to be removed.

Review Comment:
   Adds the comment of `catalogName`.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -64,9 +74,27 @@ public class TableController {
     @PostMapping("/createTable")
     public R<Void> createTable(@RequestBody TableInfo tableInfo) {
         try {
-            Catalog catalog = CatalogUtils.getCatalog(getCatalogInfo(tableInfo.getCatalogName()));
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);

Review Comment:
   ```suggestion
               log.error("Exception with creating table.", e);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -99,34 +101,35 @@ public R<List<DatabaseInfo>> getAllDatabases() {
         return R.succeed(databaseInfoList);
     }
 
-    /**
-     * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
-     *
-     * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
-     * @return The associated CatalogInfo object, or null if it doesn't exist.
-     */
-    private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
-        LambdaQueryWrapper<CatalogInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
-        return catalogService.getOne(queryWrapper);
-    }
-
     /**
      * Removes a database by its name.
      *
-     * @param databaseInfo The information of the database to be removed.
+     * @param databaseName The database to be removed.
      * @return A response indicating the success or failure of the removal operation.
      * @throws RuntimeException if the database is not found or it is not empty.
      */
-    @DeleteMapping("/delete")
-    public R<Void> remove(@RequestBody DatabaseInfo databaseInfo) {
+    @DeleteMapping("/delete/{databaseName}/{catalogName}")
+    public R<Void> remove(@PathVariable String databaseName, @PathVariable String catalogName) {
         try {
-            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
-            Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
-            DatabaseManager.dropDatabase(catalog, databaseInfo.getDatabaseName());
+            CatalogInfo catalogInfo = getCatalogInfo(catalogName);
+            PaimonService service = CatalogUtils.getPaimonService(catalogInfo);
+            service.dropDatabase(databaseName);
             return R.succeed();
-        } catch (Catalog.DatabaseNotEmptyException | Catalog.DatabaseNotExistException e) {
-            throw new RuntimeException(e);
+        } catch (Exception e) {
+            log.error("Error occurred while dropping database.", e);

Review Comment:
   ```suggestion
               log.error("Exception with dropping database.", e);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -64,9 +74,27 @@ public class TableController {
     @PostMapping("/createTable")

Review Comment:
   ```suggestion
       @PostMapping("/create")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")

Review Comment:
   ```suggestion
       @PostMapping("/column/add")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -57,15 +58,15 @@ public class DatabaseController {
     @PostMapping("/createDatabase")
     public R<Void> createDatabase(@RequestBody DatabaseInfo databaseInfo) {
         try {
-            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
-            Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
-            if (DatabaseManager.databaseExists(catalog, databaseInfo.getDatabaseName())) {
+            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo.getCatalogName());
+            PaimonService service = CatalogUtils.getPaimonService(catalogInfo);
+            if (service.databaseExists(databaseInfo.getDatabaseName())) {
                 return R.failed(Status.DATABASE_NAME_IS_EXIST, databaseInfo.getDatabaseName());
             }
-            DatabaseManager.createDatabase(catalog, databaseInfo.getDatabaseName());
+            service.createDatabase(databaseInfo.getDatabaseName());
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating database.", e);

Review Comment:
   ```suggestion
               log.error("Exception with creating database.", e);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.

Review Comment:
   Adds the comment of `alterTableRequest`.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")

Review Comment:
   ```suggestion
       @PostMapping("/alter")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")

Review Comment:
   ```suggestion
       @PostMapping("/option/add")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -101,13 +100,15 @@ public R<List<CatalogInfo>> getCatalog() {
     }
 
     /**
-     * Removes a catalog by its ID.
+     * Removes a catalog based on the catalog name.
      *
-     * @param catalogId The ID of the catalog to be removed.
-     * @return A response indicating the success or failure of the removal operation.
+     * @param catalogName The catalog name.
+     * @return R<Void/> indicating the success or failure of the operation.

Review Comment:
   ```suggestion
        * @return A response indicating the success or failure of the operation.
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java:
##########
@@ -60,10 +60,20 @@ public enum Status {
     /** ------------database-----------------. */
     DATABASE_NAME_IS_EXIST(10401, "database.name.exist"),
     DATABASE_CREATE_ERROR(10402, "database.create.error"),
+    DATABASE_REMOVE_ERROR(10403, "database.remove.error"),
 
     /** ------------table-----------------. */
     TABLE_NAME_IS_EXIST(10501, "table.name.exist"),
     TABLE_CREATE_ERROR(10502, "table.create.error"),
+    TABLE_ADD_COLUMN_ERROR(10503, "table.add.column.error"),
+    TABLE_ADD_OPTION_ERROR(10504, "table.add.option.error"),
+    TABLE_REMOVE_OPTION_ERROR(10505, "table.remove.option.error"),
+    TABLE_DROP_COLUMN_ERROR(10506, "table.drop.column.error"),
+    TABLE_AlTER_COLUMN_ERROR(10507, "table.alter.column.error"),
+    TABLE_UPDATE_COLUMN_TYPE_ERROR(10508, "table.update.column.type.error"),

Review Comment:
   Any usage?



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/data/result/enums/Status.java:
##########
@@ -60,10 +60,20 @@ public enum Status {
     /** ------------database-----------------. */
     DATABASE_NAME_IS_EXIST(10401, "database.name.exist"),
     DATABASE_CREATE_ERROR(10402, "database.create.error"),
+    DATABASE_REMOVE_ERROR(10403, "database.remove.error"),
 
     /** ------------table-----------------. */
     TABLE_NAME_IS_EXIST(10501, "table.name.exist"),
     TABLE_CREATE_ERROR(10502, "table.create.error"),
+    TABLE_ADD_COLUMN_ERROR(10503, "table.add.column.error"),
+    TABLE_ADD_OPTION_ERROR(10504, "table.add.option.error"),
+    TABLE_REMOVE_OPTION_ERROR(10505, "table.remove.option.error"),
+    TABLE_DROP_COLUMN_ERROR(10506, "table.drop.column.error"),
+    TABLE_AlTER_COLUMN_ERROR(10507, "table.alter.column.error"),
+    TABLE_UPDATE_COLUMN_TYPE_ERROR(10508, "table.update.column.type.error"),
+    TABLE_UPDATE_COLUMN_COMMENT_ERROR(10509, "table.update.column.comment.error"),
+    TABLE_DROP_ERROR(10510, "table.drop.error"),
+    TABLE_RENAME_ERROR(10510, "table.rename.error"),

Review Comment:
   ```suggestion
       TABLE_RENAME_ERROR(10510, "table.rename.error");
   ```
   Meanwhile, removes the line 77.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -57,15 +58,15 @@ public class DatabaseController {
     @PostMapping("/createDatabase")

Review Comment:
   ```suggestion
       @PostMapping("/create")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")
+    public R<Void> removeOption(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestParam String key) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            TableChange.RemoveOption removeOption = TableChange.remove(key);
+            tableChanges.add(removeOption);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while removing option.", e);
+            return R.failed(Status.TABLE_REMOVE_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Drops a table from the specified database in the given catalog.
+     *
+     * @param catalogName The name of the catalog from which the table will be dropped.
+     * @param databaseName The name of the database from which the table will be dropped.
+     * @param tableName The name of the table to be dropped.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_DROP_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @DeleteMapping("/delete/{catalogName}/{databaseName}/{tableName}")
+    public R<Void> dropTable(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            service.dropTable(databaseName, tableName);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping table.", e);
+            return R.failed(Status.TABLE_DROP_ERROR);
+        }
+    }
+
+    /**
+     * Renames a table in the specified database of the given catalog.
+     *
+     * @param catalogName The name of the catalog where the table resides.
+     * @param databaseName The name of the database where the table resides.
+     * @param fromTableName The current name of the table to be renamed.
+     * @param toTableName The new name for the table.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_RENAME_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @PostMapping("/renameTable")

Review Comment:
   ```suggestion
       @PostMapping("/rename")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/CatalogController.java:
##########
@@ -45,46 +48,42 @@ public class CatalogController {
     @Autowired private CatalogService catalogService;
 
     /**
-     * Create a filesystem catalog.
+     * Create a catalog.
      *
-     * @param catalogInfo The catalogInfo for the filesystem catalog.
+     * @param catalogInfo The catalogInfo for the catalog.
      * @return The created catalog.
      */
-    @PostMapping("/createFilesystemCatalog")
-    public R<Void> createFilesystemCatalog(@RequestBody CatalogInfo catalogInfo) {
+    @PostMapping("/createCatalog")

Review Comment:
   ```suggestion
       @PostMapping("/create")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")
+    public R<Void> removeOption(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestParam String key) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            TableChange.RemoveOption removeOption = TableChange.remove(key);
+            tableChanges.add(removeOption);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while removing option.", e);

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -99,34 +101,35 @@ public R<List<DatabaseInfo>> getAllDatabases() {
         return R.succeed(databaseInfoList);
     }
 
-    /**
-     * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
-     *
-     * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
-     * @return The associated CatalogInfo object, or null if it doesn't exist.
-     */
-    private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
-        LambdaQueryWrapper<CatalogInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
-        return catalogService.getOne(queryWrapper);
-    }
-
     /**
      * Removes a database by its name.
      *
-     * @param databaseInfo The information of the database to be removed.
+     * @param databaseName The database to be removed.
      * @return A response indicating the success or failure of the removal operation.
      * @throws RuntimeException if the database is not found or it is not empty.
      */
-    @DeleteMapping("/delete")
-    public R<Void> remove(@RequestBody DatabaseInfo databaseInfo) {
+    @DeleteMapping("/delete/{databaseName}/{catalogName}")
+    public R<Void> remove(@PathVariable String databaseName, @PathVariable String catalogName) {

Review Comment:
   ```suggestion
       public R<Void> dropDatabase(@PathVariable String databaseName, @PathVariable String catalogName) {
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -99,34 +101,35 @@ public R<List<DatabaseInfo>> getAllDatabases() {
         return R.succeed(databaseInfoList);
     }
 
-    /**
-     * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
-     *
-     * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
-     * @return The associated CatalogInfo object, or null if it doesn't exist.
-     */
-    private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
-        LambdaQueryWrapper<CatalogInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
-        return catalogService.getOne(queryWrapper);
-    }
-
     /**
      * Removes a database by its name.
      *
-     * @param databaseInfo The information of the database to be removed.
+     * @param databaseName The database to be removed.
      * @return A response indicating the success or failure of the removal operation.
      * @throws RuntimeException if the database is not found or it is not empty.
      */
-    @DeleteMapping("/delete")
-    public R<Void> remove(@RequestBody DatabaseInfo databaseInfo) {
+    @DeleteMapping("/delete/{databaseName}/{catalogName}")
+    public R<Void> remove(@PathVariable String databaseName, @PathVariable String catalogName) {
         try {
-            CatalogInfo catalogInfo = getCatalogInfo(databaseInfo);
-            Catalog catalog = CatalogUtils.getCatalog(catalogInfo);
-            DatabaseManager.dropDatabase(catalog, databaseInfo.getDatabaseName());
+            CatalogInfo catalogInfo = getCatalogInfo(catalogName);
+            PaimonService service = CatalogUtils.getPaimonService(catalogInfo);
+            service.dropDatabase(databaseName);
             return R.succeed();
-        } catch (Catalog.DatabaseNotEmptyException | Catalog.DatabaseNotExistException e) {
-            throw new RuntimeException(e);
+        } catch (Exception e) {
+            log.error("Error occurred while dropping database.", e);
+            return R.failed(Status.DATABASE_CREATE_ERROR);

Review Comment:
   ```suggestion
               return R.failed(Status.DATABASE_DROP_ERROR);
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/DatabaseController.java:
##########
@@ -99,34 +101,35 @@ public R<List<DatabaseInfo>> getAllDatabases() {
         return R.succeed(databaseInfoList);
     }
 
-    /**
-     * Retrieves the associated CatalogInfo object based on the given DatabaseInfo object.
-     *
-     * @param databaseInfo The DatabaseInfo object for which to retrieve the associated CatalogInfo.
-     * @return The associated CatalogInfo object, or null if it doesn't exist.
-     */
-    private CatalogInfo getCatalogInfo(DatabaseInfo databaseInfo) {
-        LambdaQueryWrapper<CatalogInfo> queryWrapper = new LambdaQueryWrapper<>();
-        queryWrapper.eq(CatalogInfo::getId, databaseInfo.getCatalogId());
-        return catalogService.getOne(queryWrapper);
-    }
-
     /**
      * Removes a database by its name.
      *
-     * @param databaseInfo The information of the database to be removed.
+     * @param databaseName The database to be removed.
      * @return A response indicating the success or failure of the removal operation.
      * @throws RuntimeException if the database is not found or it is not empty.
      */
-    @DeleteMapping("/delete")
-    public R<Void> remove(@RequestBody DatabaseInfo databaseInfo) {
+    @DeleteMapping("/delete/{databaseName}/{catalogName}")

Review Comment:
   ```suggestion
       @DeleteMapping("/drop/{catalogName}/{databaseName}")
   ```



##########
paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for CatalogController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogControllerTest extends ControllerTestBase {
+
+    private static final String catalogPath = "/api/catalog";
+
+    @TempDir java.nio.file.Path tempFile;
+
+    private static final String catalogName = "testCatalog";
+
+    @Test
+    @Order(1)

Review Comment:
   Why use `Order`? Could the test cases have no relation?



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")
+    public R<Void> removeOption(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestParam String key) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            TableChange.RemoveOption removeOption = TableChange.remove(key);
+            tableChanges.add(removeOption);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while removing option.", e);
+            return R.failed(Status.TABLE_REMOVE_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Drops a table from the specified database in the given catalog.
+     *
+     * @param catalogName The name of the catalog from which the table will be dropped.
+     * @param databaseName The name of the database from which the table will be dropped.
+     * @param tableName The name of the table to be dropped.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_DROP_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @DeleteMapping("/delete/{catalogName}/{databaseName}/{tableName}")
+    public R<Void> dropTable(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            service.dropTable(databaseName, tableName);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping table.", e);
+            return R.failed(Status.TABLE_DROP_ERROR);
+        }
+    }
+
+    /**
+     * Renames a table in the specified database of the given catalog.
+     *
+     * @param catalogName The name of the catalog where the table resides.
+     * @param databaseName The name of the database where the table resides.
+     * @param fromTableName The current name of the table to be renamed.
+     * @param toTableName The new name for the table.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_RENAME_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @PostMapping("/renameTable")
+    public R<Void> renameTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String fromTableName,
+            @RequestParam String toTableName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            service.renameTable(databaseName, fromTableName, toTableName);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while renaming table.", e);

Review Comment:
   Ditto.



##########
paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/DatabaseControllerTest.java:
##########
@@ -0,0 +1,91 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.DatabaseInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for DatabaseController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+public class DatabaseControllerTest extends ControllerTestBase {
+
+    private static final String databasePath = "/api/database";
+
+    private static final String catalogName = "paimon_catalog";

Review Comment:
   Could all the controll test use the same catalog name?



##########
paimon-web-server/src/main/resources/i18n/messages.properties:
##########
@@ -37,5 +37,15 @@ catalog.name.exist=This catalog name {0} is exist
 catalog.create.error=An exception is returned when calling the Paimon API to create a Catalog.
 database.name.exist=This database name {0} is exist.
 database.create.error=An exception is returned when calling the Paimon API to create a Database.
+database.remove.error=An exception is returned when calling the Paimon API to remove a Database.
 table.name.exist=This table name {0} is exist.
 table.create.error=An exception is returned when calling the Paimon API to create a Table.
+table.add.column.error=An exception is returned when calling the Paimon API to add a Column.
+table.drop.column.error=An exception is returned when calling the Paimon API to drop a Column.
+table.add.option.error=An exception is returned when calling the Paimon API to add an option.
+table.remove.option.error=An exception is returned when calling the Paimon API to remove an option.
+table.alter.column.error=An exception is returned when calling the Paimon API to alter a column.
+table.update.column.type.error=An exception is returned when calling the Paimon API to update the type of a column.

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")

Review Comment:
   ```suggestion
       @DeleteMapping("/column/drop/{catalogName}/{databaseName}/{tableName}/{columnName}")
   ```



##########
tools/maven/checkstyle.xml:
##########
@@ -323,16 +323,16 @@ This file is based on the checkstyle file of Apache Beam.
 			<property name="severity" value="error"/>
 		</module>
 
-		<module name="MemberNameCheck">
-			<!-- Validates non-static members against the supplied expression. -->
+		<!--<module name="MemberNameCheck">

Review Comment:
   Why comment this module?



##########
scripts/sql/paimon-mysql.sql:
##########
@@ -0,0 +1,174 @@
+--  Licensed to the Apache Software Foundation (ASF) under one or more
+--  contributor license agreements.  See the NOTICE file distributed with
+--  this work for additional information regarding copyright ownership.
+--  The ASF licenses this file to You under the Apache License, Version 2.0
+--  (the "License"); you may not use this file except in compliance with
+--  the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+--  Unless required by applicable law or agreed to in writing, software
+--  distributed under the License is distributed on an "AS IS" BASIS,
+--  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+--  See the License for the specific language governing permissions and
+--  limitations under the License.
+
+DROP TABLE IF EXISTS `user`;
+CREATE TABLE if not exists `user`
+(
+    `id`          int(11)      NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+    `username`    varchar(50)  NOT NULL COMMENT 'username',
+    `password`    varchar(50)  NULL     DEFAULT NULL COMMENT 'password',
+    `nickname`    varchar(50)  NULL     DEFAULT NULL COMMENT 'nickname',
+    `user_type`   int          NOT NULL DEFAULT 0 COMMENT 'login type (0:LOCAL,1:LDAP)',
+    `url`         varchar(100) NULL     DEFAULT NULL COMMENT 'avatar url',
+    `mobile`      varchar(20)  NULL     DEFAULT NULL COMMENT 'mobile phone',
+    `email`       varchar(100) NULL     DEFAULT NULL COMMENT 'email',
+    `enabled`     tinyint(1)   NOT NULL DEFAULT 1 COMMENT 'is enable',
+    `is_delete`   tinyint(1)   NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `create_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `tenant`;
+CREATE TABLE if not exists `tenant`
+(
+    `id`          int(11)      NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+    `name`        varchar(64)  NULL     DEFAULT NULL COMMENT 'tenant name',
+    `description` varchar(255) NULL     DEFAULT NULL COMMENT 'tenant description',
+    `is_delete`   tinyint(1)   NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `create_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_tenant`;
+CREATE TABLE if not exists `user_tenant`
+(
+    `id`          int(11)     NOT NULL PRIMARY KEY AUTO_INCREMENT COMMENT 'ID',
+    `user_id`     int(11)     NOT NULL COMMENT 'user id',
+    `tenant_id`   int(11)     NOT NULL COMMENT 'tenant id',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `sys_role`;
+CREATE TABLE if not exists `sys_role`
+(
+    `id`          int(11)      not null auto_increment primary key comment 'id',
+    `role_name`   varchar(30)  not null comment 'role name',
+    `role_key`    varchar(100) not null comment 'role key',
+    `sort`        int(4)       not null comment 'sort',
+    `enabled`     tinyint(1)   NOT NULL DEFAULT 1 COMMENT 'is enable',
+    `is_delete`   tinyint(1)   NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `remark`      varchar(500)          default null comment 'remark',
+    `create_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0)  NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `sys_menu`;
+CREATE TABLE if not exists `sys_menu`
+(
+    `id`          int(11)  not null auto_increment primary key comment 'id',
+    `menu_name`   varchar(50) not null comment 'menu name',
+    `parent_id`   int(11)           default 0 comment 'parent id',
+    `sort`        int(4)               default 0 comment 'sort',
+    `path`        varchar(200)         default '' comment 'route path',
+    `query`       varchar(255)         default null comment 'route params',
+    `is_cache`    int(1)               default 0 comment 'is cache(0:cache 1:no_cache)',
+    `type`        char(1)              default '' comment 'menu type(M:directory C:menu F:button)',
+    `visible`     char(1)              default 0 comment 'is visible(0:display 1:hide)',
+    `component`   varchar(255)         default null comment 'component path',
+    `is_frame`    int(1)               default 0 comment 'is frame',
+    `enabled`     tinyint(1)  NOT NULL DEFAULT 1 COMMENT 'is enable',
+    `is_delete`   tinyint(1)  NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `perms`       varchar(100)         default null comment 'menu perms',
+    `icon`        varchar(100)         default '#' comment 'menu icon',
+    `remark`      varchar(500)         default '' comment 'remark',
+    `create_time` datetime(0) NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0) NULL     DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `user_role`;
+CREATE TABLE if not exists `user_role`
+(
+    `id`          int(11)     not null auto_increment primary key comment 'id',
+    `user_id`     int(11)     not null comment 'user id',
+    `role_id`     int(11)     not null comment 'role id',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
+    unique key `idx_user_role` (`user_id`, `role_id`)
+    ) ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `role_menu`;
+CREATE TABLE if not exists `role_menu`
+(
+    `id`          int(11)     not null auto_increment primary key comment 'id',
+    `role_id`     int(11)     not null comment 'role id',
+    `menu_id`     int(11)     not null comment 'menu id',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
+    unique key `idx_role_menu` (`role_id`, `menu_id`)
+    )  ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `catalog`;
+CREATE TABLE if not exists `catalog`
+(
+    `id`          int(11)     not null auto_increment primary key comment 'id',
+    `catalog_type`     varchar(50)  not null comment 'catalog type',
+    `catalog_name`     varchar(100)     not null comment 'catalog name',
+    `warehouse`     varchar(200)     not null comment 'warehouse',
+    `hive_uri`     varchar(200)     comment 'hive uri',
+    `hive_conf_dir`     varchar(100)   comment 'catalog name',
+    `is_delete`   tinyint(1)   NOT NULL DEFAULT 0 COMMENT 'is delete',
+    `create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
+    `update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time'
+    )  ENGINE = InnoDB DEFAULT CHARSET=utf8;
+
+
+INSERT INTO `user` ( id, username, password, nickname, mobile
+                   , email, enabled, is_delete)
+VALUES ( 1, 'admin', '21232f297a57a5a743894a0e4a801fc3', 'Admin', 0
+       , 'admin@paimon.com', 1, 0);
+INSERT INTO `user` (id, username, password, nickname, mobile, email, enabled, is_delete)
+VALUES (2, 'common', '21232f297a57a5a743894a0e4a801fc3', 'common', 0, 'common@paimon.com', 1, 0);
+
+INSERT INTO `tenant` (id, name, description)
+VALUES (1, 'DefaultTenant', 'DefaultTenant');
+
+INSERT INTO `user_tenant` (`id`, `user_id`, `tenant_id`)
+VALUES (1, 1, 1);
+
+insert into sys_role (id, role_name, role_key, sort)
+values (1, 'admin', 'admin', 1),
+       (2, 'common', 'common', 2);
+
+insert into sys_menu (id, menu_name, parent_id, sort, path, component, is_frame, type, perms, icon, remark)
+values (1, 'all', 0, 1, 'system', null, 1, 'M', 'system', 'admin', 'system root path'),
+       (100, 'user manager', 1, 1, 'user', 'user/index', 1, 'C', 'system:user:list', 'user', 'user manager'),
+       (1000, 'user query', 100, 1, '', '', 1, 'F', 'system:user:query', '#', ''),
+       (1001, 'user add', 100, 2, '', '', 1, 'F', 'system:user:add', '#', ''),
+       (1002, 'user edit', 100, 3, '', '', 1, 'F', 'system:user:edit', '#', ''),
+       (1003, 'user del', 100, 4, '', '', 1, 'F', 'system:user:remove', '#', ''),
+       (1004, 'user reset', 100, 5, '', '', 1, 'F', 'system:user:resetPwd', '#', ''),
+       (200, 'role manager', 1, 1, 'role', 'role/index', 1, 'C', 'system:role:list', 'role', 'role manager'),
+       (2000, 'role query', 200, 1, '', '', 1, 'F', 'system:role:query', '#', ''),
+       (2001, 'role add', 200, 2, '', '', 1, 'F', 'system:role:add', '#', ''),
+       (2002, 'role edit', 200, 3, '', '', 1, 'F', 'system:role:edit', '#', ''),
+       (2003, 'role del', 200, 4, '', '', 1, 'F', 'system:role:remove', '#', ''),
+       (300, 'menu manager', 1, 1, 'menu', 'menu/index', 1, 'C', 'system:menu:list', 'menu', 'menu manager'),
+       (3000, 'menu query', 300, 1, '', '', 1, 'F', 'system:menu:query', '#', ''),
+       (3001, 'menu add', 300, 2, '', '', 1, 'F', 'system:menu:add', '#', ''),
+       (3002, 'menu edit', 300, 3, '', '', 1, 'F', 'system:menu:edit', '#', ''),
+       (3003, 'menu del', 300, 4, '', '', 1, 'F', 'system:menu:remove', '#', '');
+
+insert into user_role (id, user_id, role_id)
+values (1, 1, 1), (2, 2, 2);
+
+insert into role_menu (role_id, menu_id)
+values (1, 1),
+       (1, 100),
+       (1, 1000),
+       (1, 1001),
+       (1, 1002),
+       (1, 1003),
+       (1, 1004);

Review Comment:
   New line at the end.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")

Review Comment:
   ```suggestion
       @PostMapping("/option/remove")
   ```



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")
+    public R<Void> removeOption(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestParam String key) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            TableChange.RemoveOption removeOption = TableChange.remove(key);
+            tableChanges.add(removeOption);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while removing option.", e);
+            return R.failed(Status.TABLE_REMOVE_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Drops a table from the specified database in the given catalog.
+     *
+     * @param catalogName The name of the catalog from which the table will be dropped.
+     * @param databaseName The name of the database from which the table will be dropped.
+     * @param tableName The name of the table to be dropped.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_DROP_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @DeleteMapping("/delete/{catalogName}/{databaseName}/{tableName}")

Review Comment:
   ```suggestion
       @DeleteMapping("/drop/{catalogName}/{databaseName}/{tableName}")
   ```



##########
paimon-web-server/src/main/resources/application-dev-mysql.yml:
##########
@@ -0,0 +1,21 @@
+# 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.
+
+spring:
+  datasource:
+    url: jdbc:mysql://${MYSQL_ADDR:43.143.237.199:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

Review Comment:
   Why use this IP?



##########
paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for CatalogController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogControllerTest extends ControllerTestBase {
+
+    private static final String catalogPath = "/api/catalog";
+
+    @TempDir java.nio.file.Path tempFile;
+
+    private static final String catalogName = "testCatalog";
+
+    @Test
+    @Order(1)
+    public void testCreateCatalog() throws Exception {
+        CatalogInfo catalogInfo = new CatalogInfo();
+        catalogInfo.setCatalogType("filesystem");
+        catalogInfo.setCatalogName(catalogName);
+        catalogInfo.setWarehouse(tempFile.toUri().toString());
+        catalogInfo.setDelete(false);
+
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.post(catalogPath + "/createCatalog")
+                                        .cookie(cookie)
+                                        .content(ObjectMapperUtils.toJSON(catalogInfo))
+                                        .contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        .accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<Void> r = ObjectMapperUtils.fromJSON(responseString, new TypeReference<R<Void>>() {});
+        assertEquals(200, r.getCode());
+
+        mockMvc.perform(

Review Comment:
   Any assert?



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/util/CatalogUtils.java:
##########
@@ -18,11 +18,14 @@
 
 package org.apache.paimon.web.server.util;
 
-import org.apache.paimon.catalog.Catalog;
-import org.apache.paimon.web.api.catalog.CatalogCreator;
+import org.apache.paimon.web.api.catalog.PaimonService;
+import org.apache.paimon.web.api.catalog.PaimonServiceFactory;
+import org.apache.paimon.web.server.data.enums.CatalogMode;
 import org.apache.paimon.web.server.data.model.CatalogInfo;
 
-/** catalog util. */
+import org.apache.commons.lang3.StringUtils;
+
+/** Catalog util. */
 public class CatalogUtils {

Review Comment:
   Renames to `ServiceUtils` and updates the java doc.



##########
paimon-web-server/src/main/java/org/apache/paimon/web/server/controller/TableController.java:
##########
@@ -75,19 +103,279 @@ public R<Void> createTable(@RequestBody TableInfo tableInfo) {
                             .options(tableOptions)
                             .comment(tableInfo.getDescription())
                             .build();
-            if (TableManager.tableExists(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName())) {
+            if (service.tableExists(tableInfo.getDatabaseName(), tableInfo.getTableName())) {
                 return R.failed(Status.TABLE_NAME_IS_EXIST, tableInfo.getTableName());
             }
-            TableManager.createTable(
-                    catalog, tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
+            service.createTable(
+                    tableInfo.getDatabaseName(), tableInfo.getTableName(), tableMetadata);
             return R.succeed();
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("Error occurred while creating table.", e);
             return R.failed(Status.TABLE_CREATE_ERROR);
         }
     }
 
+    /**
+     * Adds a column to the table.
+     *
+     * @param tableInfo The information of the table, including the catalog name, database name,
+     *     table name, and table columns.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/addColumn")
+    public R<Void> addColumn(@RequestBody TableInfo tableInfo) {
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            List<TableColumn> tableColumns = tableInfo.getTableColumns();
+            List<TableChange> tableChanges = new ArrayList<>();
+            Map<String, String> options = new HashMap<>();
+            for (TableColumn tableColumn : tableColumns) {
+                if (tableColumn.getDefaultValue() != null
+                        && !tableColumn.getDefaultValue().equals("")) {
+                    options.put(
+                            FIELDS_PREFIX
+                                    + "."
+                                    + tableColumn.getField()
+                                    + "."
+                                    + DEFAULT_VALUE_SUFFIX,
+                            tableColumn.getDefaultValue());
+                }
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                tableColumn.getField(),
+                                DataTypeConvertUtils.convert(
+                                        new PaimonDataType(
+                                                tableColumn.getDataType().getType(),
+                                                true,
+                                                tableColumn.getDataType().getPrecision(),
+                                                tableColumn.getDataType().getScale())),
+                                tableColumn.getComment());
+                TableChange.AddColumn add = TableChange.add(columnMetadata);
+                tableChanges.add(add);
+            }
+
+            if (options.size() > 0) {
+                for (Map.Entry<String, String> entry : options.entrySet()) {
+                    TableChange.SetOption setOption =
+                            TableChange.set(entry.getKey(), entry.getValue());
+                    tableChanges.add(setOption);
+                }
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding column.", e);
+            return R.failed(Status.TABLE_ADD_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Drops a column from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param columnName The name of the column to be dropped.
+     * @return The result indicating the success or failure of the operation.
+     */
+    @DeleteMapping("/dropColumn/{catalogName}/{databaseName}/{tableName}/{columnName}")
+    public R<Void> dropColumn(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName,
+            @PathVariable String columnName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            List<TableChange> tableChanges = new ArrayList<>();
+            TableChange.DropColumn dropColumn = TableChange.dropColumn(columnName);
+            tableChanges.add(dropColumn);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while dropping column.", e);
+            return R.failed(Status.TABLE_DROP_COLUMN_ERROR);
+        }
+    }
+
+    /**
+     * Modify a column in a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @return A response indicating the success or failure of the operation.
+     */
+    @PostMapping("/alterTable")
+    public R<Void> alterTable(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestBody AlterTableRequest alterTableRequest) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+
+            TableColumn oldColumn = alterTableRequest.getOldColumn();
+            TableColumn newColumn = alterTableRequest.getNewColumn();
+
+            List<TableChange> tableChanges = createTableChanges(oldColumn, newColumn);
+
+            if (!Objects.equals(newColumn.getField(), oldColumn.getField())) {
+                ColumnMetadata columnMetadata =
+                        new ColumnMetadata(
+                                oldColumn.getField(),
+                                DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                                oldColumn.getComment());
+
+                TableChange.ModifyColumnName modifyColumnName =
+                        TableChange.modifyColumnName(columnMetadata, newColumn.getField());
+                List<TableChange> modifyNameTableChanges = new ArrayList<>();
+                modifyNameTableChanges.add(modifyColumnName);
+                service.alterTable(databaseName, tableName, modifyNameTableChanges);
+            }
+
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while altering table.", e);
+            return R.failed(Status.TABLE_AlTER_COLUMN_ERROR);
+        }
+    }
+
+    private List<TableChange> createTableChanges(TableColumn oldColumn, TableColumn newColumn) {
+        ColumnMetadata columnMetadata =
+                new ColumnMetadata(
+                        newColumn.getField(),
+                        DataTypeConvertUtils.convert(oldColumn.getDataType()),
+                        oldColumn.getComment());
+
+        TableChange.ModifyColumnType modifyColumnType =
+                TableChange.modifyColumnType(
+                        columnMetadata, DataTypeConvertUtils.convert(newColumn.getDataType()));
+
+        TableChange.ModifyColumnComment modifyColumnComment =
+                TableChange.modifyColumnComment(columnMetadata, newColumn.getComment());
+
+        List<TableChange> tableChanges = new ArrayList<>();
+        tableChanges.add(modifyColumnType);
+        tableChanges.add(modifyColumnComment);
+
+        return tableChanges;
+    }
+
+    /**
+     * Adds options to a table.
+     *
+     * @param tableInfo An object containing table information.
+     * @return If the options are successfully added, returns a successful result object. If an
+     *     exception occurs, returns a result object with an error status.
+     */
+    @PostMapping("/addOption")
+    public R<Void> addOption(@RequestBody TableInfo tableInfo) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service =
+                    CatalogUtils.getPaimonService(getCatalogInfo(tableInfo.getCatalogName()));
+            Map<String, String> tableOptions = tableInfo.getTableOptions();
+            for (Map.Entry<String, String> entry : tableOptions.entrySet()) {
+                TableChange.SetOption setOption = TableChange.set(entry.getKey(), entry.getValue());
+                tableChanges.add(setOption);
+            }
+            service.alterTable(tableInfo.getDatabaseName(), tableInfo.getTableName(), tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while adding option.", e);
+            return R.failed(Status.TABLE_ADD_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Removes an option from a table.
+     *
+     * @param catalogName The name of the catalog.
+     * @param databaseName The name of the database.
+     * @param tableName The name of the table.
+     * @param key The key of the option to be removed.
+     * @return Returns a {@link R} object indicating the success or failure of the operation. If the
+     *     option is successfully removed, the result will be a successful response with no data. If
+     *     an error occurs during the operation, the result will be a failed response with an error
+     *     code. Possible error codes: {@link Status#TABLE_REMOVE_OPTION_ERROR}.
+     */
+    @PostMapping("/removeOption")
+    public R<Void> removeOption(
+            @RequestParam String catalogName,
+            @RequestParam String databaseName,
+            @RequestParam String tableName,
+            @RequestParam String key) {
+        List<TableChange> tableChanges = new ArrayList<>();
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));
+            TableChange.RemoveOption removeOption = TableChange.remove(key);
+            tableChanges.add(removeOption);
+            service.alterTable(databaseName, tableName, tableChanges);
+            return R.succeed();
+        } catch (Exception e) {
+            log.error("Error occurred while removing option.", e);
+            return R.failed(Status.TABLE_REMOVE_OPTION_ERROR);
+        }
+    }
+
+    /**
+     * Drops a table from the specified database in the given catalog.
+     *
+     * @param catalogName The name of the catalog from which the table will be dropped.
+     * @param databaseName The name of the database from which the table will be dropped.
+     * @param tableName The name of the table to be dropped.
+     * @return A Response object indicating the success or failure of the operation. If the
+     *     operation is successful, the response will be R.succeed(). If the operation fails, the
+     *     response will be R.failed() with Status.TABLE_DROP_ERROR.
+     * @throws RuntimeException If there is an error during the operation, a RuntimeException is
+     *     thrown with the error message.
+     */
+    @DeleteMapping("/delete/{catalogName}/{databaseName}/{tableName}")
+    public R<Void> dropTable(
+            @PathVariable String catalogName,
+            @PathVariable String databaseName,
+            @PathVariable String tableName) {
+        try {
+            PaimonService service = CatalogUtils.getPaimonService(getCatalogInfo(catalogName));

Review Comment:
   Ditto.



##########
paimon-web-server/src/main/resources/application-dev-mysql.yml:
##########
@@ -0,0 +1,21 @@
+# 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.
+
+spring:
+  datasource:
+    url: jdbc:mysql://${MYSQL_ADDR:43.143.237.199:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
+    username: ${MYSQL_USERNAME:root}
+    password: ${MYSQL_PASSWORD:weipeng.?3656LL$#>}
+    driver-class-name: com.mysql.cj.jdbc.Driver

Review Comment:
   New line at the end.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [Improvement] Refactor server module [incubator-paimon-webui]

Posted by "s7monk (via GitHub)" <gi...@apache.org>.
s7monk commented on code in PR #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48#discussion_r1356592827


##########
paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for CatalogController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogControllerTest extends ControllerTestBase {
+
+    private static final String catalogPath = "/api/catalog";
+
+    @TempDir java.nio.file.Path tempFile;
+
+    private static final String catalogName = "testCatalog";
+
+    @Test
+    @Order(1)
+    public void testCreateCatalog() throws Exception {
+        CatalogInfo catalogInfo = new CatalogInfo();
+        catalogInfo.setCatalogType("filesystem");
+        catalogInfo.setCatalogName(catalogName);
+        catalogInfo.setWarehouse(tempFile.toUri().toString());
+        catalogInfo.setDelete(false);
+
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.post(catalogPath + "/createCatalog")
+                                        .cookie(cookie)
+                                        .content(ObjectMapperUtils.toJSON(catalogInfo))
+                                        .contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        .accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<Void> r = ObjectMapperUtils.fromJSON(responseString, new TypeReference<R<Void>>() {});
+        assertEquals(200, r.getCode());
+
+        mockMvc.perform(

Review Comment:
   assertEquals(200, r.getCode()): The current test verifies whether the interface is accessible and whether the parameters are correct.



##########
paimon-web-server/src/test/java/org/apache/paimon/web/server/controller/CatalogControllerTest.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.paimon.web.server.controller;
+
+import org.apache.paimon.web.server.data.model.CatalogInfo;
+import org.apache.paimon.web.server.data.result.R;
+import org.apache.paimon.web.server.util.ObjectMapperUtils;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Order;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.io.TempDir;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
+import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/** Test for CatalogController. */
+@SpringBootTest
+@AutoConfigureMockMvc
+@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
+public class CatalogControllerTest extends ControllerTestBase {
+
+    private static final String catalogPath = "/api/catalog";
+
+    @TempDir java.nio.file.Path tempFile;
+
+    private static final String catalogName = "testCatalog";
+
+    @Test
+    @Order(1)
+    public void testCreateCatalog() throws Exception {
+        CatalogInfo catalogInfo = new CatalogInfo();
+        catalogInfo.setCatalogType("filesystem");
+        catalogInfo.setCatalogName(catalogName);
+        catalogInfo.setWarehouse(tempFile.toUri().toString());
+        catalogInfo.setDelete(false);
+
+        String responseString =
+                mockMvc.perform(
+                                MockMvcRequestBuilders.post(catalogPath + "/createCatalog")
+                                        .cookie(cookie)
+                                        .content(ObjectMapperUtils.toJSON(catalogInfo))
+                                        .contentType(MediaType.APPLICATION_JSON_VALUE)
+                                        .accept(MediaType.APPLICATION_JSON_VALUE))
+                        .andExpect(MockMvcResultMatchers.status().isOk())
+                        .andDo(MockMvcResultHandlers.print())
+                        .andReturn()
+                        .getResponse()
+                        .getContentAsString();
+
+        R<Void> r = ObjectMapperUtils.fromJSON(responseString, new TypeReference<R<Void>>() {});
+        assertEquals(200, r.getCode());
+
+        mockMvc.perform(

Review Comment:
   assertEquals(200, r.getCode()): The current test verifies whether the interface is accessible and whether the parameters are correct.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [Improvement] Refactor server module [incubator-paimon-webui]

Posted by "SteNicholas (via GitHub)" <gi...@apache.org>.
SteNicholas merged PR #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


Re: [PR] [Improvement] Refactor server module [incubator-paimon-webui]

Posted by "s7monk (via GitHub)" <gi...@apache.org>.
s7monk commented on code in PR #48:
URL: https://github.com/apache/incubator-paimon-webui/pull/48#discussion_r1356579936


##########
paimon-web-server/src/main/resources/application-dev-mysql.yml:
##########
@@ -0,0 +1,21 @@
+# 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.
+
+spring:
+  datasource:
+    url: jdbc:mysql://${MYSQL_ADDR:43.143.237.199:3306}/${MYSQL_DATABASE:paimon}?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

Review Comment:
   This is the IP of our current test environment.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: issues-unsubscribe@paimon.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org