This is an automated email from the ASF dual-hosted git repository.

shaofengshi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 919dcac2c [#5059] Add commands to create, delete and modify metalakes, 
catalogs and schema in CLI. (#5117)
919dcac2c is described below

commit 919dcac2cff41ff6c3ad7e54fbd4b2d23995b71e
Author: Justin Mclean <[email protected]>
AuthorDate: Thu Nov 7 00:23:21 2024 +1100

    [#5059] Add commands to create, delete and modify metalakes, catalogs and 
schema in CLI. (#5117)
    
    ### What changes were proposed in this pull request?
    
    Add commands to create, delete and modify metalakes, catalogs and
    schema.
    
    ### Why are the changes needed?
    
    stage 2 of the CLI.
    
    Fix: #5059
    
    ### Does this PR introduce _any_ user-facing change?
    
    Add new CLI features.
    
    ### How was this patch tested?
    
    Tests pass locally.
---
 .../org/apache/gravitino/cli/CommandActions.java   |   6 +
 .../org/apache/gravitino/cli/ErrorMessages.java    |   4 +
 .../java/org/apache/gravitino/cli/FullName.java    |  16 +-
 .../apache/gravitino/cli/GravitinoCommandLine.java | 152 +++++++++++++--
 .../org/apache/gravitino/cli/GravitinoConfig.java  | 129 +++++++++++++
 .../org/apache/gravitino/cli/GravitinoOptions.java |  25 ++-
 .../main/java/org/apache/gravitino/cli/Main.java   |   5 +
 .../java/org/apache/gravitino/cli/Properties.java  |  76 ++++++++
 .../java/org/apache/gravitino/cli/Providers.java   |  92 +++++++++
 .../gravitino/cli/commands/AllMetalakeDetails.java |   1 +
 .../gravitino/cli/commands/CatalogDetails.java     |   1 +
 .../gravitino/cli/commands/ClientVersion.java      |   1 +
 .../{SchemaDetails.java => CreateCatalog.java}     |  59 +++---
 .../{MetalakeDetails.java => CreateMetalake.java}  |  28 +--
 .../{SchemaDetails.java => CreateSchema.java}      |  38 ++--
 .../{ListSchema.java => DeleteCatalog.java}        |  24 +--
 .../{MetalakeDetails.java => DeleteMetalake.java}  |  24 ++-
 .../{SchemaDetails.java => DeleteSchema.java}      |  23 +--
 .../{TableCommand.java => DeleteTable.java}        |  47 +++--
 ...alogDetails.java => ListCatalogProperties.java} |  23 +--
 .../gravitino/cli/commands/ListCatalogs.java       |   1 +
 .../apache/gravitino/cli/commands/ListColumns.java |   1 +
 ...stCatalogs.java => ListMetalakeProperties.java} |  26 +--
 .../gravitino/cli/commands/ListMetalakes.java      |   1 +
 .../{ServerVersion.java => ListProperties.java}    |  34 ++--
 .../apache/gravitino/cli/commands/ListSchema.java  |   1 +
 ...chemaDetails.java => ListSchemaProperties.java} |  24 +--
 .../apache/gravitino/cli/commands/ListTables.java  |   1 +
 .../gravitino/cli/commands/MetalakeDetails.java    |   1 +
 ...{ListSchema.java => RemoveCatalogProperty.java} |  25 +--
 ...stCatalogs.java => RemoveMetalakeProperty.java} |  29 +--
 ...chemaDetails.java => RemoveSchemaProperty.java} |  34 ++--
 .../gravitino/cli/commands/SchemaDetails.java      |   1 +
 .../gravitino/cli/commands/ServerVersion.java      |   1 +
 ...CatalogDetails.java => SetCatalogProperty.java} |  34 ++--
 ...{ListCatalogs.java => SetMetalakeProperty.java} |  32 ++--
 .../{SchemaDetails.java => SetSchemaProperty.java} |  38 ++--
 .../gravitino/cli/commands/TableCommand.java       |   1 +
 .../gravitino/cli/commands/TableDetails.java       |   1 +
 .../{ListSchema.java => UpdateCatalogComment.java} |  25 +--
 .../{ListSchema.java => UpdateCatalogName.java}    |  29 ++-
 ...istCatalogs.java => UpdateMetalakeComment.java} |  29 +--
 .../{ListCatalogs.java => UpdateMetalakeName.java} |  28 +--
 .../org/apache/gravitino/cli/PropertiesTest.java   | 112 +++++++++++
 .../apache/gravitino/cli/TestCommandActions.java   |  12 +-
 .../apache/gravitino/cli/TestCommandEntities.java  |   4 +-
 .../org/apache/gravitino/cli/TestFulllName.java    |  14 +-
 .../apache/gravitino/cli/TestGravitinoConfig.java  | 187 ++++++++++++++++++
 .../apache/gravitino/cli/TestGravitinoOptions.java |   4 +-
 .../java/org/apache/gravitino/cli/TestMain.java    |  28 ++-
 .../org/apache/gravitino/cli/TestProviders.java    | 100 ++++++++++
 docs/cli.md                                        | 212 ++++++++++++++++++---
 52 files changed, 1514 insertions(+), 330 deletions(-)

diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java
index 1077f437c..48b9bc238 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandActions.java
@@ -31,6 +31,9 @@ public class CommandActions {
   public static final String UPDATE = "update";
   public static final String CREATE = "create";
   public static final String DELETE = "delete";
+  public static final String SET = "set";
+  public static final String REMOVE = "remove";
+  public static final String PROPERTIES = "properties";
 
   private static final HashSet<String> VALID_COMMANDS = new HashSet<>();
 
@@ -40,6 +43,9 @@ public class CommandActions {
     VALID_COMMANDS.add(UPDATE);
     VALID_COMMANDS.add(CREATE);
     VALID_COMMANDS.add(DELETE);
+    VALID_COMMANDS.add(SET);
+    VALID_COMMANDS.add(REMOVE);
+    VALID_COMMANDS.add(PROPERTIES);
   }
 
   /**
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
index 847b96516..0e7816ae4 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ErrorMessages.java
@@ -23,10 +23,14 @@ package org.apache.gravitino.cli;
 public class ErrorMessages {
   public static final String UNSUPPORTED_COMMAND = "Unsupported or unknown 
command.";
   public static final String UNKNOWN_ENTITY = "Unknown entity.";
+  public static final String TOO_MANY_ARGUMENTS = "Too many arguments.";
   public static final String UNKNOWN_METALAKE = "Unknown metalake name.";
   public static final String UNKNOWN_CATALOG = "Unknown catalog name.";
   public static final String UNKNOWN_SCHEMA = "Unknown schema name.";
   public static final String UNKNOWN_TABLE = "Unknown table name.";
   public static final String MALFORMED_NAME = "Malformed entity name.";
   public static final String MISSING_NAME = "Missing name.";
+  public static final String METALAKE_EXISTS = "Metalake already exists.";
+  public static final String CATALOG_EXISTS = "Catalog already exists.";
+  public static final String SCHEMA_EXISTS = "Schema already exists.";
 }
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java
index 32df42f48..a56fdc299 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java
@@ -41,11 +41,13 @@ public class FullName {
 
   /**
    * Retrieves the metalake name from the command line options, the 
GRAVITINO_METALAKE environment
-   * variable.
+   * variable or the Gravitino config file.
    *
    * @return The metalake name, or null if not found.
    */
   public String getMetalakeName() {
+    GravitinoConfig config = new GravitinoConfig(null);
+
     // If specified on the command line use that
     if (line.hasOption(GravitinoOptions.METALAKE)) {
       return line.getOptionValue(GravitinoOptions.METALAKE);
@@ -60,6 +62,18 @@ public class FullName {
     // Check if the metalake name is set as an environment variable
     if (metalakeEnv != null) {
       return metalakeEnv;
+      // Check if the metalake name is specified in the configuration file
+    } else if (config.fileExists()) {
+      config.read();
+      String configName = config.getMetalakeName();
+      if (configName != null) {
+        return configName;
+      }
+    }
+
+    // Extract the metalake name from the full name option
+    if (line.hasOption(GravitinoOptions.NAME)) {
+      return line.getOptionValue(GravitinoOptions.NAME).split("\\.")[0];
     }
 
     return null;
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
index f942a4afd..356956387 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoCommandLine.java
@@ -19,21 +19,42 @@
 
 package org.apache.gravitino.cli;
 
+import java.util.Map;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.HelpFormatter;
 import org.apache.commons.cli.Options;
 import org.apache.gravitino.cli.commands.CatalogDetails;
 import org.apache.gravitino.cli.commands.ClientVersion;
+import org.apache.gravitino.cli.commands.CreateCatalog;
+import org.apache.gravitino.cli.commands.CreateMetalake;
+import org.apache.gravitino.cli.commands.CreateSchema;
+import org.apache.gravitino.cli.commands.DeleteCatalog;
+import org.apache.gravitino.cli.commands.DeleteMetalake;
+import org.apache.gravitino.cli.commands.DeleteSchema;
+import org.apache.gravitino.cli.commands.DeleteTable;
+import org.apache.gravitino.cli.commands.ListCatalogProperties;
 import org.apache.gravitino.cli.commands.ListCatalogs;
 import org.apache.gravitino.cli.commands.ListColumns;
+import org.apache.gravitino.cli.commands.ListMetalakeProperties;
 import org.apache.gravitino.cli.commands.ListMetalakes;
 import org.apache.gravitino.cli.commands.ListSchema;
+import org.apache.gravitino.cli.commands.ListSchemaProperties;
 import org.apache.gravitino.cli.commands.ListTables;
 import org.apache.gravitino.cli.commands.MetalakeAuditInfo;
 import org.apache.gravitino.cli.commands.MetalakeDetails;
+import org.apache.gravitino.cli.commands.RemoveCatalogProperty;
+import org.apache.gravitino.cli.commands.RemoveMetalakeProperty;
+import org.apache.gravitino.cli.commands.RemoveSchemaProperty;
 import org.apache.gravitino.cli.commands.SchemaDetails;
 import org.apache.gravitino.cli.commands.ServerVersion;
+import org.apache.gravitino.cli.commands.SetCatalogProperty;
+import org.apache.gravitino.cli.commands.SetMetalakeProperty;
+import org.apache.gravitino.cli.commands.SetSchemaProperty;
 import org.apache.gravitino.cli.commands.TableDetails;
+import org.apache.gravitino.cli.commands.UpdateCatalogComment;
+import org.apache.gravitino.cli.commands.UpdateCatalogName;
+import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
+import org.apache.gravitino.cli.commands.UpdateMetalakeName;
 
 /* Gravitino Command line */
 public class GravitinoCommandLine {
@@ -45,6 +66,8 @@ public class GravitinoCommandLine {
   private String urlEnv;
   private boolean urlSet = false;
   private boolean ignore = false;
+  private String ignoreEnv;
+  private boolean ignoreSet = false;
 
   public static final String CMD = "gcli"; // recommended name
   public static final String DEFAULT_URL = "http://localhost:8090";;
@@ -66,9 +89,26 @@ public class GravitinoCommandLine {
 
   /** Handles the parsed command line arguments and executes the corresponding 
actions. */
   public void handleCommandLine() {
+    GravitinoConfig config = new GravitinoConfig(null);
+
     /* Check if you should ignore client/version versions */
     if (line.hasOption(GravitinoOptions.IGNORE)) {
       ignore = true;
+    } else {
+      // Cache the ignore environment variable
+      if (ignoreEnv == null && !ignoreSet) {
+        ignoreEnv = System.getenv("GRAVITINO_IGNORE");
+        ignore = ignoreEnv != null && ignoreEnv.equals("true");
+        ignoreSet = true;
+      }
+
+      // Check if the ignore name is specified in the configuration file
+      if (ignoreEnv == null) {
+        if (config.fileExists()) {
+          config.read();
+          ignore = config.getIgnore();
+        }
+      }
     }
 
     executeCommand();
@@ -131,6 +171,29 @@ public class GravitinoCommandLine {
       }
     } else if (CommandActions.LIST.equals(command)) {
       new ListMetalakes(url, ignore).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      new CreateMetalake(url, ignore, metalake, comment).handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      new DeleteMetalake(url, ignore, metalake).handle();
+    } else if (CommandActions.SET.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      String value = line.getOptionValue(GravitinoOptions.VALUE);
+      new SetMetalakeProperty(url, ignore, metalake, property, value).handle();
+    } else if (CommandActions.REMOVE.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      new RemoveMetalakeProperty(url, ignore, metalake, property).handle();
+    } else if (CommandActions.PROPERTIES.equals(command)) {
+      new ListMetalakeProperties(url, ignore, metalake).handle();
+    } else if (CommandActions.UPDATE.equals(command)) {
+      if (line.hasOption(GravitinoOptions.COMMENT)) {
+        String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+        new UpdateMetalakeComment(url, ignore, metalake, comment).handle();
+      }
+      if (line.hasOption(GravitinoOptions.RENAME)) {
+        String newName = line.getOptionValue(GravitinoOptions.RENAME);
+        new UpdateMetalakeName(url, ignore, metalake, newName).handle();
+      }
     }
   }
 
@@ -142,11 +205,41 @@ public class GravitinoCommandLine {
     FullName name = new FullName(line);
     String metalake = name.getMetalakeName();
 
+    if (CommandActions.LIST.equals(command)) {
+      new ListCatalogs(url, ignore, metalake).handle();
+      return;
+    }
+
+    String catalog = name.getCatalogName();
+
     if (CommandActions.DETAILS.equals(command)) {
-      String catalog = name.getCatalogName();
       new CatalogDetails(url, ignore, metalake, catalog).handle();
-    } else if (CommandActions.LIST.equals(command)) {
-      new ListCatalogs(url, ignore, metalake).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      String provider = line.getOptionValue(GravitinoOptions.PROVIDER);
+      String properties = line.getOptionValue(GravitinoOptions.PROPERTIES);
+      Map<String, String> propertyMap = new Properties().parse(properties);
+      new CreateCatalog(url, ignore, metalake, catalog, provider, comment, 
propertyMap).handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      new DeleteCatalog(url, ignore, metalake, catalog).handle();
+    } else if (CommandActions.SET.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      String value = line.getOptionValue(GravitinoOptions.VALUE);
+      new SetCatalogProperty(url, ignore, metalake, catalog, property, 
value).handle();
+    } else if (CommandActions.REMOVE.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      new RemoveCatalogProperty(url, ignore, metalake, catalog, 
property).handle();
+    } else if (CommandActions.PROPERTIES.equals(command)) {
+      new ListCatalogProperties(url, ignore, metalake, catalog).handle();
+    } else if (CommandActions.UPDATE.equals(command)) {
+      if (line.hasOption(GravitinoOptions.COMMENT)) {
+        String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+        new UpdateCatalogComment(url, ignore, metalake, catalog, 
comment).handle();
+      }
+      if (line.hasOption(GravitinoOptions.RENAME)) {
+        String newName = line.getOptionValue(GravitinoOptions.RENAME);
+        new UpdateCatalogName(url, ignore, metalake, catalog, 
newName).handle();
+      }
     }
   }
 
@@ -159,11 +252,29 @@ public class GravitinoCommandLine {
     String metalake = name.getMetalakeName();
     String catalog = name.getCatalogName();
 
+    if (CommandActions.LIST.equals(command)) {
+      new ListSchema(url, ignore, metalake, catalog).handle();
+      return;
+    }
+
+    String schema = name.getSchemaName();
+
     if (CommandActions.DETAILS.equals(command)) {
-      String schema = name.getSchemaName();
       new SchemaDetails(url, ignore, metalake, catalog, schema).handle();
-    } else if (CommandActions.LIST.equals(command)) {
-      new ListSchema(url, ignore, metalake, catalog).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      new CreateSchema(url, ignore, metalake, catalog, schema, 
comment).handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      new DeleteSchema(url, ignore, metalake, catalog, schema).handle();
+    } else if (CommandActions.SET.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      String value = line.getOptionValue(GravitinoOptions.VALUE);
+      new SetSchemaProperty(url, ignore, metalake, catalog, schema, property, 
value).handle();
+    } else if (CommandActions.REMOVE.equals(command)) {
+      String property = line.getOptionValue(GravitinoOptions.PROPERTY);
+      new RemoveSchemaProperty(url, ignore, metalake, catalog, schema, 
property).handle();
+    } else if (CommandActions.PROPERTIES.equals(command)) {
+      new ListSchemaProperties(url, ignore, metalake, catalog, 
schema).handle();
     }
   }
 
@@ -177,11 +288,19 @@ public class GravitinoCommandLine {
     String catalog = name.getCatalogName();
     String schema = name.getSchemaName();
 
+    if (CommandActions.LIST.equals(command)) {
+      new ListTables(url, ignore, metalake, catalog, schema).handle();
+      return;
+    }
+
+    String table = name.getTableName();
+
     if (CommandActions.DETAILS.equals(command)) {
-      String table = name.getTableName();
       new TableDetails(url, ignore, metalake, catalog, schema, table).handle();
-    } else if (CommandActions.LIST.equals(command)) {
-      new ListTables(url, ignore, metalake, catalog, schema).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      // TODO
+    } else if (CommandActions.DELETE.equals(command)) {
+      new DeleteTable(url, ignore, metalake, catalog, schema, table).handle();
     }
   }
 
@@ -194,20 +313,22 @@ public class GravitinoCommandLine {
     String metalake = name.getMetalakeName();
     String catalog = name.getCatalogName();
     String schema = name.getSchemaName();
+    String table = name.getTableName();
 
     if (CommandActions.LIST.equals(command)) {
-      String table = name.getTableName();
       new ListColumns(url, ignore, metalake, catalog, schema, table).handle();
     }
   }
 
   /**
    * Retrieves the Gravitinno URL from the command line options or the 
GRAVITINO_URL environment
-   * variable.
+   * variable or the Gravitio config file.
    *
    * @return The Gravitinno URL, or null if not found.
    */
   public String getUrl() {
+    GravitinoConfig config = new GravitinoConfig(null);
+
     // If specified on the command line use that
     if (line.hasOption(GravitinoOptions.URL)) {
       return line.getOptionValue(GravitinoOptions.URL);
@@ -224,6 +345,15 @@ public class GravitinoCommandLine {
       return urlEnv;
     }
 
+    // Check if the metalake name is specified in the configuration file
+    if (config.fileExists()) {
+      config.read();
+      String configURL = config.getGravitinoURL();
+      if (configURL != null) {
+        return configURL;
+      }
+    }
+
     // Return the default localhost URL
     return DEFAULT_URL;
   }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoConfig.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoConfig.java
new file mode 100644
index 000000000..da837c21e
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoConfig.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * GravitinoConfig is a configuration class used to read and manage Gravitino 
settings. It supports
+ * setting a configuration file, reading properties like 'metalake' and 'URL', 
and verifying if the
+ * config file exists.
+ */
+public class GravitinoConfig {
+  private static String defaultFile = ".gravitino";
+  private final String configFile;
+  private String metalake;
+  private String url;
+  private boolean ignore;
+
+  /**
+   * Creates a GravitinoConfig object with a specified config file. If no file 
is provided, it
+   * defaults to `.gravitino` the user's home directory.
+   *
+   * @param file The path to the config file or null to use the default.
+   */
+  public GravitinoConfig(String file) {
+    if (file == null) {
+      configFile = System.getProperty("user.home") + "/" + defaultFile;
+    } else {
+      configFile = file;
+    }
+  }
+
+  /**
+   * Checks if the configuration file exists.
+   *
+   * @return true if the config file exists, false otherwise.
+   */
+  public boolean fileExists() {
+    File file = new File(configFile);
+    return file.exists();
+  }
+
+  /**
+   * Reads the configuration file and loads the 'metalake' and 'URL' 
properties. If the file is not
+   * found, it is ignored as the config file is optional.
+   */
+  public void read() {
+    String metalakeKey = "metalake";
+    String urlKey = "URL";
+    String ignoreKey = "ignore";
+    Properties prop = new Properties();
+
+    try (FileInputStream stream = new FileInputStream(configFile)) {
+      prop.load(stream);
+    } catch (FileNotFoundException ex) {
+      // ignore as config file is optional
+      return;
+    } catch (IOException exp) {
+      System.err.println(exp.getMessage());
+    }
+
+    if (prop.containsKey(metalakeKey)) {
+      metalake = prop.getProperty(metalakeKey);
+    }
+    if (prop.containsKey(urlKey)) {
+      url = prop.getProperty(urlKey);
+    }
+    if (prop.containsKey(ignoreKey)) {
+      ignore = prop.getProperty(ignoreKey).equals("true");
+    }
+  }
+
+  /**
+   * Retrieves the metalake name stored in the configuration.
+   *
+   * @return The metalake name or null if not set.
+   */
+  public String getMetalakeName() {
+    return metalake;
+  }
+
+  /**
+   * Retrieves the Gravitino URL stored in the configuration.
+   *
+   * @return The Gravitino URL or null if not set.
+   */
+  public String getGravitinoURL() {
+    return url;
+  }
+
+  /**
+   * Retrieves the ignore options stored in the configuration.
+   *
+   * @return The config file path.
+   */
+  public boolean getIgnore() {
+    return ignore;
+  }
+
+  /**
+   * Retrieves the path to the configuration file being used.
+   *
+   * @return The config file path.
+   */
+  public String getConfigFile() {
+    return configFile;
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
index 7accc3d2a..53b9fa2eb 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/GravitinoOptions.java
@@ -31,6 +31,12 @@ public class GravitinoOptions {
   public static final String NAME = "name";
   public static final String METALAKE = "metalake";
   public static final String IGNORE = "ignore";
+  public static final String COMMENT = "comment";
+  public static final String RENAME = "rename";
+  public static final String PROPERTY = "property";
+  public static final String VALUE = "value";
+  public static final String PROVIDER = "provider";
+  public static final String PROPERTIES = "properties";
   public static final String AUDIT = "audit";
 
   /**
@@ -44,13 +50,28 @@ public class GravitinoOptions {
     // Add options using helper method to avoid repetition
     options.addOption(createSimpleOption("h", HELP, "command help 
information"));
     options.addOption(createSimpleOption("v", VERSION, "Gravitino client 
version"));
-    options.addOption(createSimpleOption("r", SERVER, "Gravitino server 
version"));
+    options.addOption(createSimpleOption("s", SERVER, "Gravitino server 
version"));
     options.addOption(createArgOption("u", URL, "Gravitino URL (default: 
http://localhost:8090)"));
-    options.addOption(createArgOption("f", NAME, "full entity name (dot 
separated)"));
+    options.addOption(createArgOption("n", NAME, "full entity name (dot 
separated)"));
     options.addOption(createArgOption("m", METALAKE, "Metalake name"));
     options.addOption(createSimpleOption("i", IGNORE, "Ignore client/sever 
version check"));
     options.addOption(createSimpleOption("a", AUDIT, "Display audit 
information"));
 
+    // Create/update options
+    options.addOption(createArgOption("r", RENAME, "new entity name"));
+    options.addOption(createArgOption("c", COMMENT, "entity comment"));
+    options.addOption(createArgOption("P", PROPERTY, "property name"));
+    options.addOption(createArgOption("V", VALUE, "property value"));
+    options.addOption(
+        createArgOption(
+            "g", PROVIDER, "provider one of hadoop, hive, mysql, postgres, 
iceberg, kafka"));
+
+    // Properties option can have multiple values
+    Option properties =
+        createArgOption("p", PROPERTIES, "comma separated property name/value 
pairs");
+    properties.hasArgs();
+    options.addOption(properties);
+
     return options;
   }
 
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
index 68db676c5..0d0eaddf7 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Main.java
@@ -35,6 +35,11 @@ public class Main {
     try {
       CommandLine line = parser.parse(options, args);
       String entity = resolveEntity(line);
+      String[] extra = line.getArgs();
+      if (extra.length > 2) {
+        System.err.println(ErrorMessages.TOO_MANY_ARGUMENTS);
+        return;
+      }
       String command = resolveCommand(line);
       GravitinoCommandLine commandLine = new GravitinoCommandLine(line, 
options, entity, command);
 
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java
new file mode 100644
index 000000000..f9985f235
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A utility class to parse a delimited list of name-value pairs into a List 
of key-value entries.
+ *
+ * <p>This class can be used to parse strings of the format 
"key1=value1,key2=value2" where the
+ * delimiter and key-value separator can be customized.
+ */
+public class Properties {
+  private String delimiter;
+  private String keyValueSeparator;
+
+  /** Default constructor, sets the delimiter to "," and the key-value 
separator to "=". */
+  public Properties() {
+    this.delimiter = ",";
+    this.keyValueSeparator = "=";
+  }
+
+  /**
+   * Constructor that allows setting custom delimiters.
+   *
+   * @param delimiter The delimiter used to separate pairs in the input string.
+   * @param keyValueSeparator The separator used to distinguish keys from 
values in each pair.
+   */
+  public Properties(String delimiter, String keyValueSeparator) {
+    this.delimiter = delimiter;
+    this.keyValueSeparator = keyValueSeparator;
+  }
+
+  /**
+   * Parses a delimited string of name-value pairs into a map of key-value 
entries.
+   *
+   * <p>Each pair in the input string is split by the specified delimiter, and 
then each pair is
+   * further split by the key-value separator.
+   *
+   * @param input The input string containing name-value pairs.
+   * @return A map of entries, where each entry represents a key-value pair 
from the input string.
+   */
+  public Map<String, String> parse(String input) {
+    HashMap<String, String> map = new HashMap<>();
+
+    // Split the input by the delimiter into key-value pairs
+    String[] pairs = input.split(delimiter);
+    for (String pair : pairs) {
+      // Split each key-value pair by the separator
+      String[] keyValue = pair.split(keyValueSeparator, 2);
+      if (keyValue.length == 2) {
+        map.put(keyValue[0].trim(), keyValue[1].trim());
+      }
+    }
+
+    return map;
+  }
+}
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Providers.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/Providers.java
new file mode 100644
index 000000000..78b57ff61
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Providers.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import java.util.HashSet;
+import org.apache.gravitino.Catalog;
+
+/**
+ * The {@code CommandEntities} class defines a set of standard entities that 
can be used in the
+ * Gravitino CLI. It also can validate if a given entity is a valid entity.
+ */
+public class Providers {
+  public static final String HIVE = "hive";
+  public static final String HADOOP = "hadoop";
+  public static final String ICEBERG = "iceberg";
+  public static final String MYSQL = "mysql";
+  public static final String POSTGRES = "postgres";
+  public static final String KAFKA = "kafka";
+
+  private static final HashSet<String> VALID_PROVIDERS = new HashSet<>();
+
+  static {
+    VALID_PROVIDERS.add(HIVE);
+    VALID_PROVIDERS.add(HADOOP);
+    VALID_PROVIDERS.add(ICEBERG);
+    VALID_PROVIDERS.add(MYSQL);
+    VALID_PROVIDERS.add(POSTGRES);
+    VALID_PROVIDERS.add(KAFKA);
+  }
+
+  /**
+   * Checks if a given provider is a valid provider.
+   *
+   * @param provider The provider to check.
+   * @return true if the provider is valid, false otherwise.
+   */
+  public static boolean isValidProvider(String provider) {
+    return VALID_PROVIDERS.contains(provider);
+  }
+
+  public static String internal(String provider) {
+    switch (provider) {
+      case HIVE:
+        return "hive";
+      case HADOOP:
+        return "hadoop";
+      case MYSQL:
+        return "jdbc-mysql";
+      case POSTGRES:
+        return "jdbc-postgresql";
+      case ICEBERG:
+        return "lakehouse-iceberg";
+      case KAFKA:
+        return "kafka";
+      default:
+        throw new IllegalArgumentException("Unsupported provider: " + 
provider);
+    }
+  }
+
+  public static Catalog.Type catalogType(String provider) {
+    switch (provider) {
+      case HADOOP:
+        return Catalog.Type.FILESET;
+      case HIVE:
+      case MYSQL:
+      case POSTGRES:
+      case ICEBERG:
+        return Catalog.Type.RELATIONAL;
+      case KAFKA:
+        return Catalog.Type.MESSAGING;
+      default:
+        throw new IllegalArgumentException("Unsupported provider: " + 
provider);
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
index 91d5b1c1b..61df5facd 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AllMetalakeDetails.java
@@ -38,6 +38,7 @@ public class AllMetalakeDetails extends Command {
   }
 
   /** Displays the name and comment of all metalakes. */
+  @Override
   public void handle() {
     Metalake[] metalakes = new Metalake[0];
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
index 6df117d0c..41451daac 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
@@ -45,6 +45,7 @@ public class CatalogDetails extends Command {
   }
 
   /** Displays the name and details of a specified catalog. */
+  @Override
   public void handle() {
     Catalog result = null;
 
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
index 6bfa41be4..13c95d503 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ClientVersion.java
@@ -35,6 +35,7 @@ public class ClientVersion extends Command {
   }
 
   /** Displays the client version. */
+  @Override
   public void handle() {
     String version = "unknown";
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateCatalog.java
similarity index 60%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateCatalog.java
index 9229d152d..7adf16db7 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateCatalog.java
@@ -19,60 +19,69 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Schema;
+import java.util.Map;
 import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.Providers;
 import org.apache.gravitino.client.GravitinoClient;
-import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.CatalogAlreadyExistsException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchSchemaException;
-
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
 
+public class CreateCatalog extends Command {
   protected final String metalake;
   protected final String catalog;
-  protected final String schema;
+  protected final String provider;
+  protected final String comment;
+  protected final Map<String, String> properties;
 
   /**
-   * Displays the details of a schema.
+   * Create a new catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param provider The provider/type of catalog.
+   * @param comment The catalog's comment.
+   * @param properties The catalog's properties.
    */
-  public SchemaDetails(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+  public CreateCatalog(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String provider,
+      String comment,
+      Map<String, String> properties) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
-    this.schema = schema;
+    this.provider = provider;
+    this.comment = comment;
+    this.properties = properties;
   }
 
-  /** Displays the name and comments of schema. */
+  /** Create a new catalog. */
+  @Override
   public void handle() {
-    Schema result = null;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      client.createCatalog(
+          catalog,
+          Providers.catalogType(provider),
+          Providers.internal(provider),
+          comment,
+          properties);
     } catch (NoSuchMetalakeException err) {
-      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      System.err.println(ErrorMessages.METALAKE_EXISTS);
       return;
-    } catch (NoSuchCatalogException err) {
-      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
-      return;
-    } catch (NoSuchSchemaException err) {
-      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+    } catch (CatalogAlreadyExistsException err) {
+      System.err.println(ErrorMessages.CATALOG_EXISTS);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
-    }
+    System.out.println(catalog + " catalog created");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateMetalake.java
similarity index 65%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateMetalake.java
index 46d61de8c..1c8f9f353 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateMetalake.java
@@ -20,39 +20,41 @@
 package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
-import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.client.GravitinoAdminClient;
+import org.apache.gravitino.exceptions.MetalakeAlreadyExistsException;
 
-/** Displays the details of a metalake. */
-public class MetalakeDetails extends Command {
+public class CreateMetalake extends Command {
   protected final String metalake;
+  protected final String comment;
 
   /**
-   * Displays metalake details.
+   * Create a new metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
+   * @param comment The metalake's comment.
    */
-  public MetalakeDetails(String url, boolean ignoreVersions, String metalake) {
+  public CreateMetalake(String url, boolean ignoreVersions, String metalake, 
String comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
+    this.comment = comment;
   }
 
-  /** Displays the name and comment of a metalake. */
+  /** Create a new metalake. */
+  @Override
   public void handle() {
-    String comment = "";
     try {
-      GravitinoClient client = buildClient(metalake);
-      comment = client.loadMetalake(metalake).comment();
-    } catch (NoSuchMetalakeException err) {
-      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      GravitinoAdminClient client = buildAdminClient();
+      client.createMetalake(metalake, comment, null);
+    } catch (MetalakeAlreadyExistsException err) {
+      System.err.println(ErrorMessages.METALAKE_EXISTS);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(metalake + "," + comment);
+    System.out.println(metalake + " created");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateSchema.java
similarity index 72%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateSchema.java
index 9229d152d..41f3df93a 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateSchema.java
@@ -19,60 +19,62 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Schema;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
-import org.apache.gravitino.exceptions.NoSuchSchemaException;
-
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
+import org.apache.gravitino.exceptions.SchemaAlreadyExistsException;
 
+public class CreateSchema extends Command {
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
+  protected final String comment;
 
   /**
-   * Displays the details of a schema.
+   * Create a new schema.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param schema The name of the schema.
+   * @param comment The schema's comment.
    */
-  public SchemaDetails(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+  public CreateSchema(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
+    this.comment = comment;
   }
 
-  /** Displays the name and comments of schema. */
+  /** Create a new schema. */
+  @Override
   public void handle() {
-    Schema result = null;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      client.loadCatalog(catalog).asSchemas().createSchema(schema, comment, 
null);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
     } catch (NoSuchCatalogException err) {
       System.err.println(ErrorMessages.UNKNOWN_CATALOG);
       return;
-    } catch (NoSuchSchemaException err) {
-      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+    } catch (SchemaAlreadyExistsException err) {
+      System.err.println(ErrorMessages.SCHEMA_EXISTS);
       return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
-    }
+    System.out.println(schema + " created");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteCatalog.java
similarity index 79%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteCatalog.java
index a4d18a924..35658e29c 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteCatalog.java
@@ -19,38 +19,38 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/** List all schema names in a schema. */
-public class ListSchema extends Command {
+public class DeleteCatalog extends Command {
 
   protected final String metalake;
   protected final String catalog;
 
   /**
-   * Lists all schemas in a catalog.
+   * Delete a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
    */
-  public ListSchema(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public DeleteCatalog(String url, boolean ignoreVersions, String metalake, 
String catalog) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
   }
 
-  /** List all schema names in a schema. */
+  /** Delete a catalog. */
+  @Override
   public void handle() {
-    String[] schemas = new String[0];
+    boolean deleted = false;
+
     try {
       GravitinoClient client = buildClient(metalake);
-      schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
+      deleted = client.dropCatalog(catalog);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -62,8 +62,10 @@ public class ListSchema extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(schemas);
-
-    System.out.println(all.toString());
+    if (deleted) {
+      System.out.println(catalog + " deleted.");
+    } else {
+      System.out.println(catalog + " not deleted.");
+    }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteMetalake.java
similarity index 74%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteMetalake.java
index 46d61de8c..951d8e16b 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteMetalake.java
@@ -20,31 +20,31 @@
 package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/** Displays the details of a metalake. */
-public class MetalakeDetails extends Command {
+public class DeleteMetalake extends Command {
   protected final String metalake;
 
   /**
-   * Displays metalake details.
+   * Delete a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    */
-  public MetalakeDetails(String url, boolean ignoreVersions, String metalake) {
+  public DeleteMetalake(String url, boolean ignoreVersions, String metalake) {
     super(url, ignoreVersions);
     this.metalake = metalake;
   }
 
-  /** Displays the name and comment of a metalake. */
+  /** Delete a metalake. */
+  @Override
   public void handle() {
-    String comment = "";
+    boolean deleted = false;
     try {
-      GravitinoClient client = buildClient(metalake);
-      comment = client.loadMetalake(metalake).comment();
+      GravitinoAdminClient client = buildAdminClient();
+      deleted = client.dropMetalake(metalake);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -53,6 +53,10 @@ public class MetalakeDetails extends Command {
       return;
     }
 
-    System.out.println(metalake + "," + comment);
+    if (deleted) {
+      System.out.println(metalake + " deleted.");
+    } else {
+      System.out.println(metalake + " not deleted.");
+    }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteSchema.java
similarity index 82%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteSchema.java
index 9229d152d..f011acbc8 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteSchema.java
@@ -19,30 +19,28 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Schema;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
+public class DeleteSchema extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
 
   /**
-   * Displays the details of a schema.
+   * Delete a schema.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param schema The name of the schema.
    */
-  public SchemaDetails(
+  public DeleteSchema(
       String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
     super(url, ignoreVersions);
     this.metalake = metalake;
@@ -50,13 +48,14 @@ public class SchemaDetails extends Command {
     this.schema = schema;
   }
 
-  /** Displays the name and comments of schema. */
+  /** Delete a schema. */
+  @Override
   public void handle() {
-    Schema result = null;
+    boolean deleted = false;
 
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      deleted = client.loadCatalog(catalog).asSchemas().dropSchema(schema, 
false);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -71,8 +70,10 @@ public class SchemaDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
+    if (deleted) {
+      System.out.println(schema + " deleted.");
+    } else {
+      System.out.println(schema + " not deleted.");
     }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTable.java
similarity index 70%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTable.java
index cecff12f4..879dacb3c 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteTable.java
@@ -19,58 +19,75 @@
 
 package org.apache.gravitino.cli.commands;
 
+import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
-import org.apache.gravitino.rel.TableCatalog;
 
-/* Common code for all table commands. */
-public class TableCommand extends Command {
+public class DeleteTable extends Command {
 
   protected final String metalake;
   protected final String catalog;
+  protected final String schema;
+  protected final String table;
 
   /**
-   * Common code for all table commands.
+   * Delete a table.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
+   * @param schema The name of the schema.
+   * @param table The name of the table.
    */
-  public TableCommand(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public DeleteTable(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String table) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
+    this.schema = schema;
+    this.table = table;
   }
 
-  /* Overridden in parent - do nothing  */
-  public void handle() {}
+  /** Delete a table. */
+  @Override
+  public void handle() {
+    boolean deleted = false;
 
-  /**
-   * Returns the table catalog for a given metalake and catalog.
-   *
-   * @return The TableCatalog or null if an error occurs.
-   */
-  public TableCatalog tableCatalog() {
     try {
       GravitinoClient client = buildClient(metalake);
-      return 
client.loadMetalake(metalake).loadCatalog(catalog).asTableCatalog();
+      NameIdentifier name = NameIdentifier.of(schema, table);
+      deleted = client.loadCatalog(catalog).asTableCatalog().dropTable(name);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
     } catch (NoSuchCatalogException err) {
       System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
     } catch (NoSuchSchemaException err) {
       System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
+      return;
     }
 
-    return null;
+    if (deleted) {
+      System.out.println(table + " deleted.");
+    } else {
+      System.out.println(table + " not deleted.");
+    }
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogProperties.java
similarity index 79%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogProperties.java
index 6df117d0c..638c6372a 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogProperties.java
@@ -19,38 +19,41 @@
 
 package org.apache.gravitino.cli.commands;
 
+import java.util.Map;
 import org.apache.gravitino.Catalog;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-public class CatalogDetails extends Command {
+/** List the properties of a catalog. */
+public class ListCatalogProperties extends ListProperties {
 
   protected final String metalake;
   protected final String catalog;
 
   /**
-   * Displays the name and comment of a catalog.
+   * List the properties of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
    */
-  public CatalogDetails(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public ListCatalogProperties(
+      String url, boolean ignoreVersions, String metalake, String catalog) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
   }
 
-  /** Displays the name and details of a specified catalog. */
+  /** List the properties of a catalog. */
+  @Override
   public void handle() {
-    Catalog result = null;
-
+    Catalog gCatalog = null;
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog);
+      gCatalog = client.loadCatalog(catalog);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -62,9 +65,7 @@ public class CatalogDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(
-          result.name() + "," + result.type() + "," + result.provider() + "," 
+ result.comment());
-    }
+    Map<String, String> properties = gCatalog.properties();
+    printProperties(properties);
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
index 48c27e822..6925b8328 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
@@ -42,6 +42,7 @@ public class ListCatalogs extends Command {
   }
 
   /** Lists all catalogs in a metalake. */
+  @Override
   public void handle() {
     String[] catalogs = new String[0];
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java
index 4b352b557..28879ce13 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListColumns.java
@@ -51,6 +51,7 @@ public class ListColumns extends TableCommand {
   }
 
   /** Displays the details of a table's columns. */
+  @Override
   public void handle() {
     Column[] columns = null;
 
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakeProperties.java
similarity index 70%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakeProperties.java
index 48c27e822..d33494674 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakeProperties.java
@@ -19,34 +19,36 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import java.util.Map;
+import org.apache.gravitino.Metalake;
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/* Lists all catalogs in a metalake. */
-public class ListCatalogs extends Command {
+/** List the properties of a metalake. */
+public class ListMetalakeProperties extends ListProperties {
 
   protected final String metalake;
 
   /**
-   * Lists all catalogs in a metalake.
+   * List the properties of a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    */
-  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+  public ListMetalakeProperties(String url, boolean ignoreVersions, String 
metalake) {
     super(url, ignoreVersions);
     this.metalake = metalake;
   }
 
-  /** Lists all catalogs in a metalake. */
+  /** List the properties of a metalake. */
+  @Override
   public void handle() {
-    String[] catalogs = new String[0];
+    Metalake gMetalake = null;
     try {
-      GravitinoClient client = buildClient(metalake);
-      catalogs = client.listCatalogs();
+      GravitinoAdminClient client = buildAdminClient();
+      gMetalake = client.loadMetalake(metalake);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -55,8 +57,8 @@ public class ListCatalogs extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(catalogs);
+    Map<String, String> properties = gMetalake.properties();
 
-    System.out.println(all.toString());
+    printProperties(properties);
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
index e218bea2a..26668157a 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListMetalakes.java
@@ -39,6 +39,7 @@ public class ListMetalakes extends Command {
   }
 
   /** Lists all metalakes. */
+  @Override
   public void handle() {
     Metalake[] metalakes = new Metalake[0];
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListProperties.java
similarity index 61%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListProperties.java
index 456226732..56c8fb8ba 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListProperties.java
@@ -19,32 +19,38 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.client.GravitinoAdminClient;
+import java.util.Map;
 
-/** Displays the Gravitino server version. */
-public class ServerVersion extends Command {
+/** List the properties of a metalake. */
+public class ListProperties extends Command {
 
   /**
-   * Displays the server version.
+   * List the properties of an entity.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    */
-  public ServerVersion(String url, boolean ignoreVersions) {
+  public ListProperties(String url, boolean ignoreVersions) {
     super(url, ignoreVersions);
   }
 
-  /** Displays the server version. */
+  @Override
   public void handle() {
-    String version = "unknown";
-    try {
-      GravitinoAdminClient client = buildAdminClient();
-      version = client.serverVersion().version();
-    } catch (Exception exp) {
-      System.err.println(exp.getMessage());
-      return;
+    /* Do nothing */
+  }
+
+  /**
+   * List the properties of an entity.
+   *
+   * @param properties The name, value pairs of properties.
+   */
+  public void printProperties(Map<String, String> properties) {
+    StringBuilder all = new StringBuilder();
+
+    for (Map.Entry<String, String> property : properties.entrySet()) {
+      all.append(property.getKey() + "," + property.getValue() + 
System.lineSeparator());
     }
 
-    System.out.println("Apache Gravitino " + version);
+    System.out.print(all.toString());
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
index a4d18a924..7a96e053d 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
@@ -46,6 +46,7 @@ public class ListSchema extends Command {
   }
 
   /** List all schema names in a schema. */
+  @Override
   public void handle() {
     String[] schemas = new String[0];
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchemaProperties.java
similarity index 82%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchemaProperties.java
index 9229d152d..3a48196b8 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchemaProperties.java
@@ -19,6 +19,7 @@
 
 package org.apache.gravitino.cli.commands;
 
+import java.util.Map;
 import org.apache.gravitino.Schema;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
@@ -26,23 +27,23 @@ import 
org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
+/** List the properties of a catalog. */
+public class ListSchemaProperties extends ListProperties {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
 
   /**
-   * Displays the details of a schema.
+   * List the properties of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param schema The name of the schema.
    */
-  public SchemaDetails(
+  public ListSchemaProperties(
       String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
     super(url, ignoreVersions);
     this.metalake = metalake;
@@ -50,13 +51,13 @@ public class SchemaDetails extends Command {
     this.schema = schema;
   }
 
-  /** Displays the name and comments of schema. */
+  /** List the properties of a catalog. */
+  @Override
   public void handle() {
-    Schema result = null;
-
+    Schema gSchema = null;
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      gSchema = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -71,8 +72,7 @@ public class SchemaDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
-    }
+    Map<String, String> properties = gSchema.properties();
+    printProperties(properties);
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java
index d1124c48b..f5e27e608 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListTables.java
@@ -46,6 +46,7 @@ public class ListTables extends TableCommand {
   }
 
   /** List the names of all tables in a schema. */
+  @Override
   public void handle() {
     NameIdentifier[] tables = null;
     Namespace name = Namespace.of(schema);
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
index 46d61de8c..c00c90c53 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/MetalakeDetails.java
@@ -40,6 +40,7 @@ public class MetalakeDetails extends Command {
   }
 
   /** Displays the name and comment of a metalake. */
+  @Override
   public void handle() {
     String comment = "";
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveCatalogProperty.java
similarity index 75%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveCatalogProperty.java
index a4d18a924..159e01370 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveCatalogProperty.java
@@ -19,38 +19,43 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/** List all schema names in a schema. */
-public class ListSchema extends Command {
+/** Remove a property of a catalog. */
+public class RemoveCatalogProperty extends Command {
 
   protected final String metalake;
   protected final String catalog;
+  protected final String property;
 
   /**
-   * Lists all schemas in a catalog.
+   * Remove a property of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
+   * @param property The name of the property.
    */
-  public ListSchema(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public RemoveCatalogProperty(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String property) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
+    this.property = property;
   }
 
-  /** List all schema names in a schema. */
+  /** Remove a property of a catalog. */
+  @Override
   public void handle() {
-    String[] schemas = new String[0];
     try {
       GravitinoClient client = buildClient(metalake);
-      schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
+      CatalogChange change = CatalogChange.removeProperty(property);
+      client.alterCatalog(catalog, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -62,8 +67,6 @@ public class ListSchema extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(schemas);
-
-    System.out.println(all.toString());
+    System.out.println(property + " property removed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveMetalakeProperty.java
similarity index 67%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveMetalakeProperty.java
index 48c27e822..894b0e53e 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveMetalakeProperty.java
@@ -19,34 +19,39 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/* Lists all catalogs in a metalake. */
-public class ListCatalogs extends Command {
+/** Remove a property of a metalake. */
+public class RemoveMetalakeProperty extends Command {
 
   protected final String metalake;
+  protected final String property;
 
   /**
-   * Lists all catalogs in a metalake.
+   * Remove a property of a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
+   * @param property The name of the property.
    */
-  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+  public RemoveMetalakeProperty(
+      String url, boolean ignoreVersions, String metalake, String property) {
     super(url, ignoreVersions);
     this.metalake = metalake;
+    this.property = property;
   }
 
-  /** Lists all catalogs in a metalake. */
+  /** Remove a property of a metalake. */
+  @Override
   public void handle() {
-    String[] catalogs = new String[0];
     try {
-      GravitinoClient client = buildClient(metalake);
-      catalogs = client.listCatalogs();
+      GravitinoAdminClient client = buildAdminClient();
+      MetalakeChange change = MetalakeChange.removeProperty(property);
+      client.alterMetalake(metalake, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -55,8 +60,6 @@ public class ListCatalogs extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(catalogs);
-
-    System.out.println(all.toString());
+    System.out.println(property + " property removed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveSchemaProperty.java
similarity index 74%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveSchemaProperty.java
index 9229d152d..12678d83a 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/RemoveSchemaProperty.java
@@ -19,44 +19,52 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Schema;
+import org.apache.gravitino.SchemaChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
+/** Remove a property of a schema. */
+public class RemoveSchemaProperty extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
+  protected final String property;
 
   /**
-   * Displays the details of a schema.
+   * Remove a property of a schema.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param schema The name of the schema.
+   * @param property The name of the property.
    */
-  public SchemaDetails(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+  public RemoveSchemaProperty(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String property) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
+    this.property = property;
   }
 
-  /** Displays the name and comments of schema. */
+  /** Remove a property of a schema. */
+  @Override
   public void handle() {
-    Schema result = null;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      SchemaChange change = SchemaChange.removeProperty(property);
+      client.loadCatalog(catalog).asSchemas().alterSchema(schema, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -71,8 +79,6 @@ public class SchemaDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
-    }
+    System.out.println(property + " property removed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
index 9229d152d..ef7b5fe9a 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
@@ -51,6 +51,7 @@ public class SchemaDetails extends Command {
   }
 
   /** Displays the name and comments of schema. */
+  @Override
   public void handle() {
     Schema result = null;
 
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
index 456226732..51488f0b6 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ServerVersion.java
@@ -35,6 +35,7 @@ public class ServerVersion extends Command {
   }
 
   /** Displays the server version. */
+  @Override
   public void handle() {
     String version = "unknown";
     try {
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetCatalogProperty.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetCatalogProperty.java
index 6df117d0c..141dae3d0 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CatalogDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetCatalogProperty.java
@@ -19,38 +19,51 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Catalog;
+import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-public class CatalogDetails extends Command {
+/** Set a property of a catalog. */
+public class SetCatalogProperty extends Command {
 
   protected final String metalake;
   protected final String catalog;
+  protected final String property;
+  protected final String value;
 
   /**
-   * Displays the name and comment of a catalog.
+   * Set a property of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
+   * @param property The name of the property.
+   * @param value The value of the property.
    */
-  public CatalogDetails(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public SetCatalogProperty(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String property,
+      String value) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
+    this.property = property;
+    this.value = value;
   }
 
-  /** Displays the name and details of a specified catalog. */
+  /** Set a property of a catalog. */
+  @Override
   public void handle() {
-    Catalog result = null;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog);
+      CatalogChange change = CatalogChange.setProperty(property, value);
+      client.alterCatalog(catalog, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -62,9 +75,6 @@ public class CatalogDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(
-          result.name() + "," + result.type() + "," + result.provider() + "," 
+ result.comment());
-    }
+    System.out.println(catalog + " property set.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetMetalakeProperty.java
similarity index 64%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetMetalakeProperty.java
index 48c27e822..8b2a1bb8f 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetMetalakeProperty.java
@@ -19,34 +19,42 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/* Lists all catalogs in a metalake. */
-public class ListCatalogs extends Command {
+/** Set a property of a metalake. */
+public class SetMetalakeProperty extends Command {
 
   protected final String metalake;
+  protected final String property;
+  protected final String value;
 
   /**
-   * Lists all catalogs in a metalake.
+   * Set a property of a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
+   * @param property The name of the property.
+   * @param value The value of the property.
    */
-  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+  public SetMetalakeProperty(
+      String url, boolean ignoreVersions, String metalake, String property, 
String value) {
     super(url, ignoreVersions);
     this.metalake = metalake;
+    this.property = property;
+    this.value = value;
   }
 
-  /** Lists all catalogs in a metalake. */
+  /** Set a property of a metalake. */
+  @Override
   public void handle() {
-    String[] catalogs = new String[0];
     try {
-      GravitinoClient client = buildClient(metalake);
-      catalogs = client.listCatalogs();
+      GravitinoAdminClient client = buildAdminClient();
+      MetalakeChange change = MetalakeChange.setProperty(property, value);
+      client.alterMetalake(metalake, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -55,8 +63,6 @@ public class ListCatalogs extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(catalogs);
-
-    System.out.println(all.toString());
+    System.out.println(metalake + " property set.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetSchemaProperty.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetSchemaProperty.java
index 9229d152d..ef71a3dc2 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SchemaDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/SetSchemaProperty.java
@@ -19,44 +19,56 @@
 
 package org.apache.gravitino.cli.commands;
 
-import org.apache.gravitino.Schema;
+import org.apache.gravitino.SchemaChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 
-/** Displays the details of schema. */
-public class SchemaDetails extends Command {
+/** Set a property of a schema. */
+public class SetSchemaProperty extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
+  protected final String property;
+  protected final String value;
 
   /**
-   * Displays the details of a schema.
+   * Set a property of a schema.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
-   * @param schema The name of the schenma.
+   * @param schema The name of the schema.
+   * @param property The name of the property.
+   * @param value The value of the property.
    */
-  public SchemaDetails(
-      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+  public SetSchemaProperty(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String property,
+      String value) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
+    this.property = property;
+    this.value = value;
   }
 
-  /** Displays the name and comments of schema. */
+  /** Set a property of a schema. */
+  @Override
   public void handle() {
-    Schema result = null;
-
     try {
       GravitinoClient client = buildClient(metalake);
-      result = client.loadCatalog(catalog).asSchemas().loadSchema(schema);
+      SchemaChange change = SchemaChange.setProperty(property, value);
+      client.loadCatalog(catalog).asSchemas().alterSchema(schema, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -71,8 +83,6 @@ public class SchemaDetails extends Command {
       return;
     }
 
-    if (result != null) {
-      System.out.println(result.name() + "," + result.comment());
-    }
+    System.out.println(schema + " property set.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
index cecff12f4..a292a2f5b 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableCommand.java
@@ -48,6 +48,7 @@ public class TableCommand extends Command {
   }
 
   /* Overridden in parent - do nothing  */
+  @Override
   public void handle() {}
 
   /**
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java
index fb3158529..2169ccaaa 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/TableDetails.java
@@ -51,6 +51,7 @@ public class TableDetails extends TableCommand {
   }
 
   /** Displays the details of a table. */
+  @Override
   public void handle() {
     Table gTable = null;
 
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogComment.java
similarity index 75%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogComment.java
index a4d18a924..eae5e0d11 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogComment.java
@@ -19,38 +19,43 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/** List all schema names in a schema. */
-public class ListSchema extends Command {
+/** Update the comment of a catalog. */
+public class UpdateCatalogComment extends Command {
 
   protected final String metalake;
   protected final String catalog;
+  protected final String comment;
 
   /**
-   * Lists all schemas in a catalog.
+   * Update the comment of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
+   * @param comment New metalake comment.
    */
-  public ListSchema(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public UpdateCatalogComment(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
+    this.comment = comment;
   }
 
-  /** List all schema names in a schema. */
+  /** Update the comment of a catalog. */
+  @Override
   public void handle() {
-    String[] schemas = new String[0];
     try {
       GravitinoClient client = buildClient(metalake);
-      schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
+      CatalogChange change = CatalogChange.updateComment(comment);
+      client.alterCatalog(catalog, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -62,8 +67,6 @@ public class ListSchema extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(schemas);
-
-    System.out.println(all.toString());
+    System.out.println(catalog + " comment changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
similarity index 72%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
index a4d18a924..f314d0759 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListSchema.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateCatalogName.java
@@ -19,51 +19,50 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.CatalogChange;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
-import org.apache.gravitino.exceptions.NoSuchCatalogException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/** List all schema names in a schema. */
-public class ListSchema extends Command {
+/** Update the name of a catalog. */
+public class UpdateCatalogName extends Command {
 
   protected final String metalake;
   protected final String catalog;
+  protected final String name;
 
   /**
-   * Lists all schemas in a catalog.
+   * Update the name of a catalog.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
    * @param catalog The name of the catalog.
+   * @param name The new metalake name.
    */
-  public ListSchema(String url, boolean ignoreVersions, String metalake, 
String catalog) {
+  public UpdateCatalogName(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String name) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
+    this.name = name;
   }
 
-  /** List all schema names in a schema. */
+  /** Update the name of a catalog. */
+  @Override
   public void handle() {
-    String[] schemas = new String[0];
     try {
       GravitinoClient client = buildClient(metalake);
-      schemas = client.loadCatalog(catalog).asSchemas().listSchemas();
+      CatalogChange change = CatalogChange.rename(name);
+      client.alterCatalog(catalog, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
-    } catch (NoSuchCatalogException err) {
-      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
-      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    String all = Joiner.on(",").join(schemas);
-
-    System.out.println(all.toString());
+    System.out.println(catalog + " name changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeComment.java
similarity index 67%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeComment.java
index 48c27e822..ae4412587 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeComment.java
@@ -19,34 +19,39 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/* Lists all catalogs in a metalake. */
-public class ListCatalogs extends Command {
+/** Update the comment of a metalake. */
+public class UpdateMetalakeComment extends Command {
 
   protected final String metalake;
+  protected final String comment;
 
   /**
-   * Lists all catalogs in a metalake.
+   * Update the comment of a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
+   * @param comment New metalake comment.
    */
-  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+  public UpdateMetalakeComment(
+      String url, boolean ignoreVersions, String metalake, String comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
+    this.comment = comment;
   }
 
-  /** Lists all catalogs in a metalake. */
+  /** Update the comment of a metalake. */
+  @Override
   public void handle() {
-    String[] catalogs = new String[0];
     try {
-      GravitinoClient client = buildClient(metalake);
-      catalogs = client.listCatalogs();
+      GravitinoAdminClient client = buildAdminClient();
+      MetalakeChange change = MetalakeChange.updateComment(comment);
+      client.alterMetalake(metalake, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -55,8 +60,6 @@ public class ListCatalogs extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(catalogs);
-
-    System.out.println(all.toString());
+    System.out.println(metalake + " comment changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeName.java
similarity index 69%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeName.java
index 48c27e822..48ef26a92 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListCatalogs.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateMetalakeName.java
@@ -19,34 +19,38 @@
 
 package org.apache.gravitino.cli.commands;
 
-import com.google.common.base.Joiner;
+import org.apache.gravitino.MetalakeChange;
 import org.apache.gravitino.cli.ErrorMessages;
-import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.client.GravitinoAdminClient;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 
-/* Lists all catalogs in a metalake. */
-public class ListCatalogs extends Command {
+/** Update the name of a metalake. */
+public class UpdateMetalakeName extends Command {
 
   protected final String metalake;
+  protected final String name;
 
   /**
-   * Lists all catalogs in a metalake.
+   * Update the name of a metalake.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
    * @param metalake The name of the metalake.
+   * @param name The new metalake name.
    */
-  public ListCatalogs(String url, boolean ignoreVersions, String metalake) {
+  public UpdateMetalakeName(String url, boolean ignoreVersions, String 
metalake, String name) {
     super(url, ignoreVersions);
     this.metalake = metalake;
+    this.name = name;
   }
 
-  /** Lists all catalogs in a metalake. */
+  /** Update the name of a metalake. */
+  @Override
   public void handle() {
-    String[] catalogs = new String[0];
     try {
-      GravitinoClient client = buildClient(metalake);
-      catalogs = client.listCatalogs();
+      GravitinoAdminClient client = buildAdminClient();
+      MetalakeChange change = MetalakeChange.rename(name);
+      client.alterMetalake(metalake, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -55,8 +59,6 @@ public class ListCatalogs extends Command {
       return;
     }
 
-    String all = Joiner.on(",").join(catalogs);
-
-    System.out.println(all.toString());
+    System.out.println(metalake + " name changed.");
   }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/PropertiesTest.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/PropertiesTest.java
new file mode 100644
index 000000000..cdf08a59d
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/PropertiesTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Map;
+import org.junit.jupiter.api.Test;
+
+public class PropertiesTest {
+
+  @Test
+  public void testDefaultDelimiterAndSeparator() {
+    Properties properties = new Properties();
+    String input = "key1=value1,key2=value2,key3=value3";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(3, result.size());
+    assertEquals("value1", result.get("key1"));
+    assertEquals("value2", result.get("key2"));
+    assertEquals("value3", result.get("key3"));
+  }
+
+  @Test
+  public void testCustomDelimiterAndSeparator() {
+    Properties properties = new Properties(";", ":");
+    String input = "key1:value1;key2:value2;key3:value3";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(3, result.size());
+    assertEquals("value1", result.get("key1"));
+    assertEquals("value2", result.get("key2"));
+    assertEquals("value3", result.get("key3"));
+  }
+
+  @Test
+  public void testEmptyInput() {
+    Properties properties = new Properties();
+    String input = "";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertTrue(result.isEmpty(), "Result should be empty for empty input");
+  }
+
+  @Test
+  public void testSinglePair() {
+    Properties properties = new Properties();
+    String input = "key1=value1";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(1, result.size());
+    assertEquals("value1", result.get("key1"));
+  }
+
+  @Test
+  public void testMalformedPair() {
+    Properties properties = new Properties();
+    String input = "key1=value1,key2,key3=value3";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(2, result.size());
+    assertEquals("value1", result.get("key1"));
+    assertEquals("value3", result.get("key3"));
+  }
+
+  @Test
+  public void testWhitespaceHandling() {
+    Properties properties = new Properties();
+    String input = " key1 = value1 , key2 = value2 ";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(2, result.size());
+    assertEquals("value1", result.get("key1"));
+    assertEquals("value2", result.get("key2"));
+  }
+
+  @Test
+  public void testDuplicateKeys() {
+    Properties properties = new Properties();
+    String input = "key1=value1,key1=value2,key2=value3";
+
+    Map<String, String> result = properties.parse(input);
+
+    assertEquals(2, result.size());
+    assertEquals("value2", result.get("key1"), "Last value should overwrite 
previous ones");
+    assertEquals("value3", result.get("key2"));
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java
index a79f9961f..58f7f9773 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandActions.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
 public class TestCommandActions {
 
   @Test
-  public void ValidCommands() {
+  public void validCommands() {
     assertTrue(
         CommandActions.isValidCommand(CommandActions.DETAILS), "DETAILS should 
be a valid command");
     assertTrue(
@@ -38,6 +38,12 @@ public class TestCommandActions {
         CommandActions.isValidCommand(CommandActions.CREATE), "CREATE should 
be a valid command");
     assertTrue(
         CommandActions.isValidCommand(CommandActions.DELETE), "DELETE should 
be a valid command");
+    assertTrue(CommandActions.isValidCommand(CommandActions.SET), "SET should 
be a valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.REMOVE), "REMOVE should 
be a valid command");
+    assertTrue(
+        CommandActions.isValidCommand(CommandActions.PROPERTIES),
+        "PROPERTIES should be a valid command");
   }
 
   @Test
@@ -62,8 +68,6 @@ public class TestCommandActions {
 
   @Test
   public void caseSensitive() {
-    assertFalse(
-        CommandActions.isValidCommand("DETAILS".toUpperCase()),
-        "Commands should be case-sensitive");
+    assertFalse(CommandActions.isValidCommand("DETAILS"), "Commands should be 
case-sensitive");
   }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
index 423834bfc..49ee9770b 100644
--- 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
@@ -62,8 +62,6 @@ public class TestCommandEntities {
 
   @Test
   public void caseSensitive() {
-    assertFalse(
-        CommandEntities.isValidEntity("DETAILS".toUpperCase()),
-        "Entities should be case-sensitive");
+    assertFalse(CommandEntities.isValidEntity("METALAKE"), "Entities should be 
case-sensitive");
   }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
index a53778437..27108fd67 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFulllName.java
@@ -17,14 +17,16 @@
  * under the License.
  */
 
+package org.apache.gravitino.cli;
+
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.MissingArgumentException;
 import org.apache.commons.cli.Options;
-import org.apache.gravitino.cli.FullName;
-import org.apache.gravitino.cli.GravitinoOptions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -65,7 +67,7 @@ public class TestFulllName {
 
   @Test
   public void malformedName() throws Exception {
-    String[] args = {"--name", "metalake.catalog"};
+    String[] args = {"--name", "catalog.schema"};
     CommandLine commandLine = new DefaultParser().parse(options, args);
     FullName fullName = new FullName(commandLine);
     String tableName = fullName.getTableName();
@@ -74,6 +76,12 @@ public class TestFulllName {
 
   @Test
   public void missingName() throws Exception {
+    String[] args = {"catalog", "--name"};
+    assertThrows(MissingArgumentException.class, () -> new 
DefaultParser().parse(options, args));
+  }
+
+  @Test
+  public void missingArgs() throws Exception {
     String[] args = {}; // No name provided
     CommandLine commandLine = new DefaultParser().parse(options, args);
     FullName fullName = new FullName(commandLine);
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoConfig.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoConfig.java
new file mode 100644
index 000000000..b2c1ce7d5
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoConfig.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.Properties;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class TestGravitinoConfig {
+
+  private static final String TEMP_FILE_PATH =
+      System.getProperty("java.io.tmpdir") + "/gravitino.properties";
+  private static final String NON_EXISTENT_FILE_PATH =
+      System.getProperty("java.io.tmpdir") + "/non_existent.properties";
+  private static final String METALAKE_KEY = "metalake";
+  private static final String METALAKE_VALUE = "metalake_demo";
+  private static final String URL_KEY = "URL";
+  private static final String URL_VALUE = "http://10.0.0.1:8090";;
+  private static final String IGNORE_KEY = "ignore";
+  private static final String IGNORE_VALUE = "true";
+
+  @BeforeEach
+  public void setUp() throws IOException {
+    // Set up a temporary config file
+    File tempFile = new File(TEMP_FILE_PATH);
+    if (tempFile.exists()) {
+      tempFile.delete();
+    }
+
+    Properties props = new Properties();
+    props.setProperty(METALAKE_KEY, METALAKE_VALUE);
+    props.setProperty(URL_KEY, URL_VALUE);
+    props.setProperty(IGNORE_KEY, IGNORE_VALUE);
+
+    try (Writer writer = Files.newBufferedWriter(tempFile.toPath(), 
StandardCharsets.UTF_8)) {
+      props.store(writer, "Test Config");
+    }
+  }
+
+  @Test
+  public void defaultPath() {
+    GravitinoConfig config = new GravitinoConfig(null);
+    String expectedFilePath = System.getProperty("user.home") + "/.gravitino";
+    assertEquals(
+        expectedFilePath,
+        config.getConfigFile(),
+        "Config file path should default to the home directory with 
'.gravitino'");
+  }
+
+  @Test
+  public void fileDoesExist() {
+    GravitinoConfig config = new GravitinoConfig(TEMP_FILE_PATH);
+    assertTrue(config.fileExists(), "Config file should exist");
+  }
+
+  @Test
+  public void fileDoesNotExist() {
+    GravitinoConfig config = new GravitinoConfig(NON_EXISTENT_FILE_PATH);
+    assertFalse(config.fileExists(), "Config file should not exist");
+  }
+
+  @Test
+  public void validConfigFile() {
+    GravitinoConfig config = new GravitinoConfig(TEMP_FILE_PATH);
+    config.read();
+    assertEquals(
+        METALAKE_VALUE,
+        config.getMetalakeName(),
+        "Should read the metalake value from the config file");
+    assertEquals(
+        URL_VALUE, config.getGravitinoURL(), "Should read the URL value from 
the config file");
+    assertTrue(config.getIgnore(), "Should read the ignore value from the 
config file");
+  }
+
+  @Test
+  public void fileNotFound() {
+    GravitinoConfig config = new GravitinoConfig(NON_EXISTENT_FILE_PATH);
+    config.read(); // No exception should be thrown, config file is optional
+    assertNull(config.getMetalakeName(), "Metalake should be null if config 
file is not found");
+  }
+
+  @Test
+  public void configFileMissingMetalake() throws IOException {
+    // Create a config file without the "metalake" key
+    File tempFileWithoutMetalake =
+        new File(System.getProperty("java.io.tmpdir") + 
"/no_metalake.properties");
+    try (Writer writer =
+        Files.newBufferedWriter(tempFileWithoutMetalake.toPath(), 
StandardCharsets.UTF_8)) {
+      writer.write("# This config file has no metalake key\n");
+    }
+
+    GravitinoConfig config = new 
GravitinoConfig(tempFileWithoutMetalake.getAbsolutePath());
+    config.read();
+    assertNull(
+        config.getMetalakeName(),
+        "Metalake should be null if the key is missing in the config file");
+
+    tempFileWithoutMetalake.delete();
+  }
+
+  @Test
+  public void configFileMissingURL() throws IOException {
+    // Create a config file without the "url" key
+    File tempFileWithoutURL = new File(System.getProperty("java.io.tmpdir") + 
"/no_url.properties");
+    try (Writer writer =
+        Files.newBufferedWriter(tempFileWithoutURL.toPath(), 
StandardCharsets.UTF_8)) {
+      writer.write("# This config file has no url key\n");
+    }
+
+    GravitinoConfig config = new 
GravitinoConfig(tempFileWithoutURL.getAbsolutePath());
+    config.read();
+    assertNull(
+        config.getGravitinoURL(), "URL should be null if the key is missing in 
the config file");
+
+    tempFileWithoutURL.delete();
+  }
+
+  @Test
+  public void configFileMissingIgnore() throws IOException {
+    // Create a config file without the "ignore" key
+    File tempFileWithoutIgnore =
+        new File(System.getProperty("java.io.tmpdir") + "/no_url.properties");
+    try (Writer writer =
+        Files.newBufferedWriter(tempFileWithoutIgnore.toPath(), 
StandardCharsets.UTF_8)) {
+      writer.write("# This config file has no ignore key\n");
+    }
+
+    GravitinoConfig config = new 
GravitinoConfig(tempFileWithoutIgnore.getAbsolutePath());
+    config.read();
+    assertFalse(
+        config.getIgnore(), "Ignore should be false if the key is missing in 
the config file");
+
+    tempFileWithoutIgnore.delete();
+  }
+
+  @Test
+  public void invalidConfigFile() throws IOException {
+    // Create a corrupt config file
+    File corruptConfigFile =
+        new File(System.getProperty("java.io.tmpdir") + 
"/config_corrupt.properties");
+    try (Writer writer =
+        Files.newBufferedWriter(corruptConfigFile.toPath(), 
StandardCharsets.UTF_8)) {
+      writer.write("corrupt content = @@%@");
+    }
+
+    GravitinoConfig config = new 
GravitinoConfig(corruptConfigFile.getAbsolutePath());
+    config.read(); // Should not throw an exception but handle it gracefully
+    assertNull(config.getMetalakeName(), "Metalake should be null if the 
config file is corrupt");
+
+    corruptConfigFile.delete();
+  }
+
+  @Test
+  public void withoutReadingConfig() {
+    GravitinoConfig config = new GravitinoConfig(TEMP_FILE_PATH);
+    assertNull(config.getMetalakeName(), "Metalake should be null before 
reading the config file");
+    assertNull(config.getGravitinoURL(), "URL should be null before reading 
the config file");
+    assertFalse(config.getIgnore(), "Ignore should be null before reading the 
config file");
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java
index 6fd12276d..879283aaf 100644
--- 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestGravitinoOptions.java
@@ -29,7 +29,7 @@ import org.junit.jupiter.api.Test;
 public class TestGravitinoOptions {
 
   @Test
-  public void testCreateSimpleOption() {
+  public void createSimpleOption() {
     GravitinoOptions gravitinoOptions = new GravitinoOptions();
     Option helpOption =
         gravitinoOptions.createSimpleOption("h", GravitinoOptions.HELP, "help 
message");
@@ -44,7 +44,7 @@ public class TestGravitinoOptions {
   }
 
   @Test
-  public void testCreateArgOption() {
+  public void createArgOption() {
     GravitinoOptions gravitinoOptions = new GravitinoOptions();
     Option urlOption = gravitinoOptions.createArgOption("u", 
GravitinoOptions.URL, "url argument");
 
diff --git a/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java
index 388d5da73..c6d1bacdb 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestMain.java
@@ -55,7 +55,7 @@ public class TestMain {
   }
 
   @Test
-  public void withTwoArgsOnly() throws ParseException {
+  public void withTwoArgs() throws ParseException {
     Options options = new GravitinoOptions().options();
     CommandLineParser parser = new DefaultParser();
     String[] args = {"metalake", "details"};
@@ -93,6 +93,19 @@ public class TestMain {
     assertNull(entity);
   }
 
+  @Test
+  public void withNoArgsAndOptions() throws ParseException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {"--name", "metalake_demo"};
+    CommandLine line = parser.parse(options, args);
+
+    String command = Main.resolveCommand(line);
+    assertNull(command);
+    String entity = Main.resolveEntity(line);
+    assertNull(entity);
+  }
+
   @Test
   @SuppressWarnings("DefaultCharset")
   public void withHelpOption() throws ParseException, 
UnsupportedEncodingException {
@@ -117,4 +130,17 @@ public class TestMain {
     assertTrue(errContent.toString().contains("Error parsing command line")); 
// Expect error
     assertTrue(outContent.toString().contains("usage:")); // Expect help output
   }
+
+  @Test
+  public void catalogWithOneArg() throws ParseException {
+    Options options = new GravitinoOptions().options();
+    CommandLineParser parser = new DefaultParser();
+    String[] args = {"catalog", "--name", "catalog_postgres"};
+    CommandLine line = parser.parse(options, args);
+
+    String command = Main.resolveCommand(line);
+    assertEquals(CommandActions.DETAILS, command);
+    String entity = Main.resolveEntity(line);
+    assertEquals(CommandEntities.CATALOG, entity);
+  }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestProviders.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestProviders.java
new file mode 100644
index 000000000..403e45883
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestProviders.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TestProviders {
+
+  @Test
+  public void validProviders() {
+    assertTrue(Providers.isValidProvider(Providers.HIVE), "HIVE should be a 
valid entity");
+    assertTrue(Providers.isValidProvider(Providers.HADOOP), "HADOOP should be 
a valid entity");
+    assertTrue(Providers.isValidProvider(Providers.ICEBERG), "ICEBERG should 
be a valid entity");
+    assertTrue(Providers.isValidProvider(Providers.MYSQL), "MYSQL should be a 
valid entity");
+    assertTrue(Providers.isValidProvider(Providers.POSTGRES), "POSTGRES should 
be a valid entity");
+    assertTrue(Providers.isValidProvider(Providers.KAFKA), "KAFKA should be a 
valid entity");
+  }
+
+  @Test
+  public void invalidProvider() {
+    assertFalse(
+        Providers.isValidProvider("invalidEntity"), "An invalid provider 
should return false");
+  }
+
+  @Test
+  public void nullEntity() {
+    assertFalse(
+        Providers.isValidProvider(null), "Null should return false as it's not 
a valid provider");
+  }
+
+  @Test
+  public void emptyEntity() {
+    assertFalse(
+        Providers.isValidProvider(""),
+        "Empty string should return false as it's not a valid entity");
+  }
+
+  @Test
+  public void caseSensitive() {
+    assertFalse(Providers.isValidProvider("HIVE"), "Providers should be 
case-sensitive");
+  }
+
+  @Test
+  public void internalNotNull() {
+    assertNotNull(Providers.internal(Providers.HIVE), "Internal string should 
not be null");
+    assertNotNull(Providers.internal(Providers.HADOOP), "Internal string 
should not be null");
+    assertNotNull(Providers.internal(Providers.ICEBERG), "Internal string 
should not be null");
+    assertNotNull(Providers.internal(Providers.MYSQL), "Internal string should 
not be null");
+    assertNotNull(Providers.internal(Providers.POSTGRES), "Internal string 
should not be null");
+    assertNotNull(Providers.internal(Providers.KAFKA), "Internal string should 
not be null");
+  }
+
+  @Test
+  public void internalNull() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> Providers.internal("unknown"),
+        "Expected an IllegalArgumentException for an unknown provider");
+  }
+
+  @Test
+  public void catalogTypeNotNull() {
+    assertNotNull(Providers.catalogType(Providers.HIVE), "Catalog type should 
not be null");
+    assertNotNull(Providers.catalogType(Providers.HADOOP), "Catalog type 
should not be null");
+    assertNotNull(Providers.catalogType(Providers.ICEBERG), "Catalog type 
should not be null");
+    assertNotNull(Providers.catalogType(Providers.MYSQL), "Catalog type should 
not be null");
+    assertNotNull(Providers.catalogType(Providers.POSTGRES), "Catalog type 
should not be null");
+    assertNotNull(Providers.catalogType(Providers.KAFKA), "Catalog type should 
not be null");
+  }
+
+  @Test
+  public void catalogTypeNull() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> Providers.catalogType("unknown"),
+        "Expected an IllegalArgumentException for an unknown provider");
+  }
+}
diff --git a/docs/cli.md b/docs/cli.md
index 98a311fef..589129815 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -8,54 +8,65 @@ last_update:
 license: 'This software is licensed under the Apache License version 2.'
 ---
 
-This document primarily outlines how users can manage metadata within Apache 
Gravitino using the Command Line Interface (CLI). The CLI is accessible via a 
terminal window as an alternative to writing code or using the REST interface.
+This document provides guidance on managing metadata within Apache Gravitino 
using the Command Line Interface (CLI). The CLI offers a terminal based 
alternative to using code or the REST interface for metadata management.
 
-Currently, you can view basic metadata information for metalakes, catalogs, 
schema, and tables. The ability to create, update, and delete metalakes and 
support additional entities in planned in the near future.
+Currently, the CLI allows users to view metadata information for metalakes, 
catalogs, schemas, and tables. Future updates will expand on these capabilities 
to include roles, users, and tags.
 
 ## Running the CLI
 
-You can set up an alias for the command like so:
+You can configure an alias for the CLI for ease of use, with the following 
command:
 
 ```bash
 alias gcli='java -jar 
../../cli/build/libs/gravitino-cli-*-incubating-SNAPSHOT.jar'
 ```
 
-Or use the `gcli.sh` script found in the `clients/cli/bin/` directory to run 
the CLI.
+Or you use the `gcli.sh` script found in the `clients/cli/bin/` directory to 
run the CLI.
 
 ## Usage
 
- To run the Gravitino CLI, use the following command structure:
+The general structure for running commands with the Gravitino CLI is `gcli 
entity command [options]`.
 
  ```bash
- usage: gcli [metalake|catalog|schema|table] 
[list|details|create|delete|update] [options]
+ usage: gcli [metalake|catalog|schema|table|column] 
[list|details|create|delete|update|set|remove|properties] [options]
  Options
- -f,--name <arg>       full entity name (dot separated)
- -h,--help             command help information
- -i,--ignore           Ignore client/sever version check
- -m,--metalake <arg>   Metalake name
- -r,--server           Gravitino server version
- -u,--url <arg>        Gravitino URL (default: http://localhost:8090)
- -v,--version          Gravitino client version
+ -c,--comment <arg>      entity comment
+ -g,--provider <arg>     provider one of hadoop, hive, mysql, postgres,
+                         iceberg, kafka
+ -h,--help               command help information
+ -i,--ignore             Ignore client/sever version check
+ -m,--metalake <arg>     Metalake name
+ -n,--name <arg>         full entity name (dot separated)
+ -P,--property <arg>     property name
+ -p,--properties <arg>   comma separated property name/value pairs
+ -r,--rename <arg>       new entity name
+ -s,--server             Gravitino server version
+ -u,--url <arg>          Gravitino URL (default: http://localhost:8090)
+ -v,--version            Gravitino client version
+ -V,--value <arg>        property value
  ```
 
 ## Commands
 
-The following commands are available for entity management:
+The following commands are used for entity management:
 
 - list: List available entities
 - details: Show detailed information about an entity
 - create: Create a new entity
 - delete: Delete an existing entity
 - update: Update an existing entity
+- set: Set a property on an entity
+- remove: Remove a property from an entity
+- properties: Display an entities properties
 
 ### Setting the Metalake name
 
-As dealing with one Metalake is a typical scenario, you can set the Metalake 
name in several ways.
+As dealing with one Metalake is a typical scenario, you can set the Metalake 
name in several ways so it doesn't need to be passed on the command line.
 
 1. Passed in on the command line via the `--metalake` parameter.
 2. Set via the `GRAVITINO_METALAKE` environment variable.
+3. Stored in the Gravitino CLI configuration file.
 
-The command line option overrides the environment variable.
+The command line option overrides the environment variable and the environment 
variable overrides the configuration file.
 
 ## Setting the Gravitino URL
 
@@ -63,8 +74,29 @@ As you need to set the Gravitino URL for every command, you 
can set the URL in s
 
 1. Passed in on the command line via the `--url` parameter.
 2. Set via the 'GRAVITINO_URL' environment variable.
+3. Stored in the Gravitino CLI configuration file.
 
-The command line option overrides the environment variable.
+The command line option overrides the environment variable and the environment 
variable overrides the configuration file.
+
+## Gravitino CLI configuration file
+
+The gravitino CLI can read commonly used CLI options from a configuration 
file. By default, the file is `.gravitino` in the user's home directory. The 
metalake, URL and ignore parameters can be set in this file.
+
+```text
+#
+# Gravitino CLI configuration file
+#
+
+# Metalake to use
+metalake=metalake_demo
+
+# Gravitino server to connect to
+URL=http://localhost:8090
+
+# Ignore client/server version mismatch
+ignore=true
+
+```
 
 ## Manage metadata
 
@@ -96,9 +128,13 @@ gcli --server
 
 ### Client/server version mismatch
 
-If the client and server are running different versions of the Gravitino 
software then you need an additional `--ignore` option for the command to run.
+If the client and server are running different versions of the Gravitino 
software then you may need to ignore the client/server version check for the 
command to run. This can be done in several ways:
+
+1. Passed in on the command line via the `--ignore` parameter.
+2. Set via the `GRAVITINO_IGNORE` environment variable.
+3. Stored in the Gravitino CLI configuration file.
 
-### Metalake
+### Metalake commands
 
 #### Show all metalakes
 
@@ -109,7 +145,49 @@ gcli metalake list
 #### Show a metalake details
 
 ```bash
-gcli metalake details --metalake metalake_demo 
+gcli metalake details --metalake metalake_demo
+```
+
+#### Create a metalake
+
+```bash
+gcli metalake create --metalake my_metalake --comment "This is my metalake"
+```
+
+#### Delete a metalake
+
+```bash
+gcli metalake delete --metalake my_metalake
+```
+
+#### Rename a metalake
+
+```bash
+gcli metalake update --metalake metalake_demo --rename demo
+```
+
+#### Update a metalake's comment
+
+```bash
+gcli metalake update --metalake metalake_demo --comment "new comment"
+```
+
+#### Display a metalake's properties
+
+```bash
+gcli metalake properties --metalake metalake_demo
+```
+
+#### Set a metalake's property
+
+```bash
+gcli metalake set --metalake metalake_demo --property test --value value
+```
+
+#### Remove a metalake's property
+
+```bash
+gcli metalake remove --metalake metalake_demo --property test
 ```
 
 ### Catalog
@@ -120,12 +198,82 @@ gcli metalake details --metalake metalake_demo
 gcli catalog list --metalake metalake_demo
 ```
 
-#### Show a catalogs details
+#### Show a catalog details
 
 ```bash
 gcli catalog details --metalake metalake_demo --name catalog_postgres
 ```
 
+#### Creating a catalog
+
+The type of catalog to be created is specified by the `--provider` option. 
Different catalogs require different properties, for example, a Hive catalog 
requires a metastore-uri property.
+
+##### Create a Hive catalog
+
+```bash
+gcli catalog create --metalake metalake_demo --name hive --provider hive 
--properties metastore.uris=thrift://hive-host:9083
+```
+
+##### Create an Iceberg catalog
+
+```bash
+gcli catalog create --metalake metalake_demo -name iceberg --provider iceberg 
--properties 
uri=thrift://hive-host:9083,catalog-backend=hive,warehouse=hdfs://hdfs-host:9000/user/iceberg/warehouse
+```
+
+##### Create a MySQL catalog
+
+```bash
+gcli catalog create --metalake metalake_demo -name mysql --provider mysql 
--properties 
jdbc-url=jdbc:mysql://mysql-host:3306?useSSL=false,jdbc-user=user,jdbc-password=password,jdbc-driver=com.mysql.cj.jdbc.Driver
+```
+
+##### Create a Postgres catalog
+
+```bash
+gcli catalog create --metalake metalake_demo -name postgres --provider 
postgres --properties 
jdbc-url=jdbc:postgresql://postgresql-host/mydb,jdbc-user=user,jdbc-password=password,jdbc-database=db,jdbc-driver=org.postgresql.Driver
+```
+
+##### Create a Kafka catalog
+
+```bash
+gcli catalog create --metalake metalake_demo -name kafka --provider kafka 
--properties bootstrap.servers=127.0.0.1:9092,127.0.0.2:9092
+```
+
+#### Delete a catalog
+
+```bash
+gcli catalog delete --metalake metalake_demo --name hive
+```
+
+#### Rename a catalog
+
+```bash
+gcli catalog update --metalake metalake_demo --name catalog_mysql --rename 
mysql
+```
+
+#### Change a catalog comment
+
+```bash
+gcli catalog update --metalake metalake_demo --name catalog_mysql --comment 
"new comment"
+```
+
+#### Display a catalog's properties
+
+```bash
+gcli catalog properties --metalake metalake_demo --name catalog_mysql
+```
+
+#### Set a catalog's property
+
+```bash
+gcli catalog set --metalake metalake_demo --name catalog_mysql --property test 
--value value
+```
+
+#### Remove a catalog's property
+
+```bash
+gcli catalog remove --metalake metalake_demo --name catalog_mysql --property 
test
+```
+
 ### Schema
 
 #### Show all schemas in a catalog
@@ -134,12 +282,26 @@ gcli catalog details --metalake metalake_demo --name 
catalog_postgres
 gcli schema list --metalake metalake_demo --name catalog_postgres
 ```
 
-#### Show a schema details
+#### Show schema details
 
 ```bash
 gcli schema details --metalake metalake_demo --name catalog_postgres.hr
 ```
 
+#### Create a schema
+
+```bash
+gcli schema create --metalake metalake_demo --name catalog_postgres.new_db
+```
+
+#### Display schema properties
+
+```bash
+gcli schema properties --metalake metalake_demo --name catalog_postgres.hr -i
+```
+
+Setting and removing schema properties is not currently supported by the Java 
API or the Gravitino CLI.
+
 ### Table
 
 #### Show all tables
@@ -153,3 +315,9 @@ gcli table list --metalake metalake_demo --name 
catalog_postgres.hr
 ```bash
 gcli column list --metalake metalake_demo --name 
catalog_postgres.hr.departments
 ```
+
+#### Delete a table
+
+```bash
+gcli table delete --metalake metalake_demo --name catalog_postgres.hr.salaries
+```

Reply via email to