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 758cd2ea7 [#5383] Add column commands to the Gravitino CLI (#5716)
758cd2ea7 is described below

commit 758cd2ea7c9479cff42408834375d38b96888a03
Author: Justin Mclean <[email protected]>
AuthorDate: Tue Dec 3 20:09:18 2024 +1100

    [#5383] Add column commands to the Gravitino CLI (#5716)
    
    ### What changes were proposed in this pull request?
    
    Add column commands to the Gravitino CLI
    
    ### Why are the changes needed?
    
    Expand Gravitino CLI.
    
    Fix: #5383
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, but add extra commands.
    
    ### How was this patch tested?
    
    locally.
---
 .../org/apache/gravitino/cli/DefaultConverter.java |  48 ++++
 .../org/apache/gravitino/cli/ErrorMessages.java    |   2 +
 .../java/org/apache/gravitino/cli/FullName.java    |  20 +-
 .../apache/gravitino/cli/GravitinoCommandLine.java |  80 ++++++
 .../org/apache/gravitino/cli/GravitinoOptions.java |  38 ++-
 .../java/org/apache/gravitino/cli/ParseType.java   |  73 +++++
 .../java/org/apache/gravitino/cli/ParsedType.java  | 100 +++++++
 .../apache/gravitino/cli/PositionConverter.java    |  43 +++
 .../apache/gravitino/cli/TestableCommandLine.java  | 140 ++++++++++
 .../org/apache/gravitino/cli/TypeConverter.java    | 121 +++++++++
 .../{UpdateTableComment.java => AddColumn.java}    |  61 ++++-
 .../{UpdateTableComment.java => DeleteColumn.java} |  33 ++-
 ...Comment.java => UpdateColumnAutoIncrement.java} |  37 ++-
 ...eTableComment.java => UpdateColumnComment.java} |  31 ++-
 ...TableComment.java => UpdateColumnDatatype.java} |  40 ++-
 ...eTableComment.java => UpdateColumnDefault.java} |  44 ++-
 ...{UpdateTableName.java => UpdateColumnName.java} |  31 ++-
 ...leComment.java => UpdateColumnNullability.java} |  37 ++-
 ...TableComment.java => UpdateColumnPosition.java} |  40 ++-
 .../gravitino/cli/commands/UpdateTableComment.java |   1 +
 .../gravitino/cli/commands/UpdateTableName.java    |   1 +
 .../apache/gravitino/cli/TestColumnCommands.java   | 301 +++++++++++++++++++++
 .../apache/gravitino/cli/TestDefaultConverter.java |  76 ++++++
 .../org/apache/gravitino/cli/TestFulllName.java    |  21 +-
 .../org/apache/gravitino/cli/TestParseType.java    |  67 +++++
 .../gravitino/cli/TestPositionConverter.java       |  82 ++++++
 .../apache/gravitino/cli/TestTypeConverter.java    |  99 +++++++
 docs/cli.md                                        |  41 ++-
 28 files changed, 1592 insertions(+), 116 deletions(-)

diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java
new file mode 100644
index 000000000..29b0b3475
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/DefaultConverter.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.apache.gravitino.rel.types.Type;
+
+public class DefaultConverter {
+
+  /**
+   * Converts a default value string to the appropriate internal default value.
+   *
+   * @param defaultValue The string representing the default value i.e. 
"current_timestamp" or a
+   *     literal value.
+   * @param dataType The string representing the default type.
+   * @return An instance of the appropriate default value.
+   */
+  public static Expression convert(String defaultValue, String dataType) {
+    Type convertedDatatype = ParseType.toType(dataType);
+
+    if (defaultValue == null || defaultValue.isEmpty()) {
+      return Column.DEFAULT_VALUE_NOT_SET;
+    } else if (defaultValue.equalsIgnoreCase("current_timestamp")) {
+      return Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP;
+    } else {
+      return Literals.of(defaultValue, convertedDatatype);
+    }
+  }
+}
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 2a34e21cc..351a40fc4 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
@@ -41,6 +41,8 @@ public class ErrorMessages {
   public static final String MULTIPLE_TAG_COMMAND_ERROR =
       "Error: The current command only supports one --tag option.";
   public static final String TAG_EXISTS = "Tag already exists.";
+  public static final String UNKNOWN_COLUMN = "Unknown column.";
+  public static final String COLUMN_EXISTS = "Column already exists.";
   public static final String UNKNOWN_TOPIC = "Unknown topic.";
   public static final String TOPIC_EXISTS = "Topic already exists.";
   public static final String UNKNOWN_FILESET = "Unknown fileset.";
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 959282485..c53a5adc8 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
@@ -118,12 +118,21 @@ public class FullName {
   /**
    * Retrieves the fileset name from the third part of the full name option.
    *
-   * @return The table name, or null if not found.
+   * @return The fileset name, or null if not found.
    */
   public String getFilesetName() {
     return getNamePart(2);
   }
 
+  /**
+   * Retrieves the column name from the fourth part of the full name option.
+   *
+   * @return The column name, or null if not found.
+   */
+  public String getColumnName() {
+    return getNamePart(3);
+  }
+
   /**
    * Helper method to retrieve a specific part of the full name based on the 
position of the part.
    *
@@ -193,4 +202,13 @@ public class FullName {
   public boolean hasTableName() {
     return hasNamePart(3);
   }
+
+  /**
+   * Does the column name exist?
+   *
+   * @return True if the column name exists, or false if it does not.
+   */
+  public boolean hasColumnName() {
+    return hasNamePart(4);
+  }
 }
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 33eca22b3..dd9091543 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
@@ -487,9 +487,89 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
     String catalog = name.getCatalogName();
     String schema = name.getSchemaName();
     String table = name.getTableName();
+    String column = name.getColumnName();
 
     if (CommandActions.LIST.equals(command)) {
       newListColumns(url, ignore, metalake, catalog, schema, table).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      String datatype = line.getOptionValue(GravitinoOptions.DATATYPE);
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      String position = line.getOptionValue(GravitinoOptions.POSITION);
+      boolean nullable = true;
+      boolean autoIncrement = false;
+      String defaultValue = line.getOptionValue(GravitinoOptions.DEFAULT);
+
+      if (line.hasOption(GravitinoOptions.NULL)) {
+        nullable = line.getOptionValue(GravitinoOptions.NULL).equals("true");
+      }
+
+      if (line.hasOption(GravitinoOptions.AUTO)) {
+        autoIncrement = 
line.getOptionValue(GravitinoOptions.AUTO).equals("true");
+      }
+
+      newAddColumn(
+              url,
+              ignore,
+              metalake,
+              catalog,
+              schema,
+              table,
+              column,
+              datatype,
+              comment,
+              position,
+              nullable,
+              autoIncrement,
+              defaultValue)
+          .handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      newDeleteColumn(url, ignore, metalake, catalog, schema, table, 
column).handle();
+    } else if (CommandActions.UPDATE.equals(command)) {
+      if (line.hasOption(GravitinoOptions.COMMENT)) {
+        String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+        newUpdateColumnComment(url, ignore, metalake, catalog, schema, table, 
column, comment)
+            .handle();
+      }
+      if (line.hasOption(GravitinoOptions.RENAME)) {
+        String newName = line.getOptionValue(GravitinoOptions.RENAME);
+        newUpdateColumnName(url, ignore, metalake, catalog, schema, table, 
column, newName)
+            .handle();
+      }
+      if (line.hasOption(GravitinoOptions.DATATYPE)) {
+        String datatype = line.getOptionValue(GravitinoOptions.DATATYPE);
+        newUpdateColumnDatatype(url, ignore, metalake, catalog, schema, table, 
column, datatype)
+            .handle();
+      }
+      if (line.hasOption(GravitinoOptions.POSITION)) {
+        String position = line.getOptionValue(GravitinoOptions.POSITION);
+        newUpdateColumnPosition(url, ignore, metalake, catalog, schema, table, 
column, position)
+            .handle();
+      }
+      if (line.hasOption(GravitinoOptions.NULL)) {
+        if (line.getOptionValue(GravitinoOptions.NULL).equals("true")) {
+          newUpdateColumnNullability(url, ignore, metalake, catalog, schema, 
table, column, true)
+              .handle();
+        } else if (line.getOptionValue(GravitinoOptions.NULL).equals("false")) 
{
+          newUpdateColumnNullability(url, ignore, metalake, catalog, schema, 
table, column, false)
+              .handle();
+        }
+      }
+      if (line.hasOption(GravitinoOptions.AUTO)) {
+        if (line.getOptionValue(GravitinoOptions.AUTO).equals("true")) {
+          newUpdateColumnAutoIncrement(url, ignore, metalake, catalog, schema, 
table, column, true)
+              .handle();
+        } else if (line.getOptionValue(GravitinoOptions.AUTO).equals("false")) 
{
+          newUpdateColumnAutoIncrement(url, ignore, metalake, catalog, schema, 
table, column, false)
+              .handle();
+        }
+      }
+      if (line.hasOption(GravitinoOptions.DEFAULT)) {
+        String defaultValue = line.getOptionValue(GravitinoOptions.DEFAULT);
+        String dataType = line.getOptionValue(GravitinoOptions.DATATYPE);
+        newUpdateColumnDefault(
+                url, ignore, metalake, catalog, schema, table, column, 
defaultValue, dataType)
+            .handle();
+      }
     }
   }
 
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 8cf5f0cc1..1fcc9fedf 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
@@ -40,6 +40,12 @@ public class GravitinoOptions {
   public static final String USER = "user";
   public static final String GROUP = "group";
   public static final String TAG = "tag";
+  public static final String DATATYPE = "datatype";
+  public static final String POSITION = "position";
+  public static final String NULL = "null";
+  public static final String AUTO = "auto";
+  public static final String DEFAULT = "default";
+  public static final String FILESET = "fileset";
   public static final String OWNER = "owner";
   public static final String ROLE = "role";
   public static final String AUDIT = "audit";
@@ -68,11 +74,11 @@ public class GravitinoOptions {
     options.addOption(createSimpleOption("a", AUDIT, "display audit 
information"));
     options.addOption(createSimpleOption("x", INDEX, "display index 
information"));
     options.addOption(createSimpleOption("d", DISTRIBUTION, "display 
distribution information"));
-    options.addOption(createSimpleOption(null, PARTITION, "display partition 
information"));
+    options.addOption(createSimpleOption(PARTITION, "display partition 
information"));
     options.addOption(createSimpleOption("o", OWNER, "display entity owner"));
 
     // Create/update options
-    options.addOption(createArgOption(null, RENAME, "new entity name"));
+    options.addOption(createArgOption(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"));
@@ -81,6 +87,11 @@ public class GravitinoOptions {
             "z", PROVIDER, "provider one of hadoop, hive, mysql, postgres, 
iceberg, kafka"));
     options.addOption(createArgOption("l", USER, "user name"));
     options.addOption(createArgOption("g", GROUP, "group name"));
+    options.addOption(createArgOption(DATATYPE, "column data type"));
+    options.addOption(createArgOption(POSITION, "position of column"));
+    options.addOption(createArgOption(NULL, "column value can be null 
(true/false)"));
+    options.addOption(createArgOption(AUTO, "column value auto-increments 
(true/false)"));
+    options.addOption(createArgOption(DEFAULT, "default column value"));
     options.addOption(createSimpleOption("o", OWNER, "display entity owner"));
     options.addOption(createArgOption("r", ROLE, "role name"));
 
@@ -108,6 +119,17 @@ public class GravitinoOptions {
     return new Option(shortName, longName, false, description);
   }
 
+  /**
+   * Helper method to create an Option that does not require arguments.
+   *
+   * @param longName The long option name.
+   * @param description The option description.
+   * @return The Option object.
+   */
+  public Option createSimpleOption(String longName, String description) {
+    return new Option(null, longName, false, description);
+  }
+
   /**
    * Helper method to create an Option that requires an argument.
    *
@@ -120,6 +142,17 @@ public class GravitinoOptions {
     return new Option(shortName, longName, true, description);
   }
 
+  /**
+   * Helper method to create an Option that requires an argument.
+   *
+   * @param longName The long option name.
+   * @param description The option description.
+   * @return The Option object.
+   */
+  public Option createArgOption(String longName, String description) {
+    return new Option(null, longName, true, description);
+  }
+
   /**
    * Helper method to create an Option that requires multiple argument.
    *
@@ -129,7 +162,6 @@ public class GravitinoOptions {
    * @return The Option object.
    */
   public Option createArgsOption(String shortName, String longName, String 
description) {
-    // Support multiple arguments
     return 
Option.builder().option(shortName).longOpt(longName).hasArgs().desc(description).build();
   }
 }
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ParseType.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ParseType.java
new file mode 100644
index 000000000..e797d0552
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ParseType.java
@@ -0,0 +1,73 @@
+/*
+ * 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.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.gravitino.rel.types.Type;
+
+public class ParseType {
+
+  /**
+   * Parses a data type string and returns a {@link 
org.apache.gravitino.cli.ParsedType} object
+   * containing the type name and length or type name, precision, and scale if 
applicable.
+   *
+   * <p>This method supports SQL style types in the format of 
"typeName(length)" or
+   * "typeName(precision, scale)". For example, "varchar(10)" and 
"decimal(10,5)" are valid inputs.
+   *
+   * @param datatype The data type string to parse e.g. "varchar(10)" or 
"decimal(10,5)".
+   * @return a {@link org.apache.gravitino.cli.ParsedType} object representing 
the parsed type name.
+   * @throws IllegalArgumentException if the data type format is unsupported 
or malformed
+   */
+  public static ParsedType parse(String datatype) {
+    Pattern pattern = Pattern.compile("^(\\w+)\\((\\d+)(?:,(\\d+))?\\)$");
+    Matcher matcher = pattern.matcher(datatype);
+
+    if (matcher.matches()) {
+      String typeName = matcher.group(1);
+      Integer lengthOrPrecision = Integer.parseInt(matcher.group(2));
+      Integer scale = matcher.group(3) != null ? 
Integer.parseInt(matcher.group(3)) : null;
+
+      if (lengthOrPrecision != null && scale != null) {
+        return new ParsedType(typeName, lengthOrPrecision, scale);
+      } else if (lengthOrPrecision != null) {
+        return new ParsedType(typeName, lengthOrPrecision);
+      } else {
+        throw new IllegalArgumentException("Unsupported/malformed data type: " 
+ typeName);
+      }
+    }
+
+    return null;
+  }
+
+  public static Type toType(String datatype) {
+    ParsedType parsed = parse(datatype);
+
+    if (parsed != null) {
+      if (parsed.getPrecision() != null && parsed.getScale() != null) {
+        return TypeConverter.convert(datatype, parsed.getPrecision(), 
parsed.getScale());
+      } else if (parsed.getLength() != null) {
+        return TypeConverter.convert(datatype, parsed.getLength());
+      }
+    }
+
+    return TypeConverter.convert(datatype);
+  }
+}
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/ParsedType.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/ParsedType.java
new file mode 100644
index 000000000..6e30c6ed3
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/ParsedType.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;
+
+public class ParsedType {
+  private String typeName;
+  private Integer length;
+  private Integer precision;
+  private Integer scale;
+
+  /**
+   * Constructs a ParsedType with specified type name, precision, and scale.
+   *
+   * @param typeName The name of the data type.
+   * @param precision The precision of the data type, which defines the total 
number of digits.
+   * @param scale The scale of the data type, which defines the number of 
digits to the right of the
+   *     decimal point.
+   */
+  public ParsedType(String typeName, Integer precision, Integer scale) {
+    this.typeName = typeName;
+    this.precision = precision;
+    this.scale = scale;
+  }
+
+  /*
+   * Constructs a ParsedType with specified type name and length.
+   *
+   * @param typeName The name of the data type.
+   * @param length The length of the data type, which typically defines the 
maximum number of characters.
+   */
+  public ParsedType(String typeName, Integer length) {
+    this.typeName = typeName;
+    this.length = length;
+  }
+
+  /**
+   * Gets the type name (e.g., "varchar" or "decimal").
+   *
+   * @return the type name
+   */
+  public String getTypeName() {
+    return typeName;
+  }
+
+  /**
+   * Gets the length for types like "varchar".
+   *
+   * @return the length, or null if not applicable
+   */
+  public Integer getLength() {
+    return length;
+  }
+
+  /**
+   * Gets the precision for types like "decimal".
+   *
+   * @return the precision, or null if not applicable
+   */
+  public Integer getPrecision() {
+    return precision;
+  }
+
+  /**
+   * Gets the scale for types like "decimal".
+   *
+   * @return the scale, or null if not applicable
+   */
+  public Integer getScale() {
+    return scale;
+  }
+
+  /** Returns a string representation of this {@code ParsedType} object. */
+  @Override
+  public String toString() {
+    if (length != null) {
+      return String.format("Type: %s, Length: %d", typeName, length);
+    } else if (precision != null && scale != null) {
+      return String.format("Type: %s, Precision: %d, Scale: %d", typeName, 
precision, scale);
+    } else {
+      return "Unsupported type";
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/PositionConverter.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/PositionConverter.java
new file mode 100644
index 000000000..324e70f4b
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/PositionConverter.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.gravitino.rel.TableChange;
+
+public class PositionConverter {
+
+  /**
+   * Converts a position string to the appropriate position.
+   *
+   * @param position The string representing the position i.e. "first" or a 
column name.
+   * @return An instance of the appropriate position.
+   * @throws IllegalArgumentException if the type name is not recognized.
+   */
+  public static TableChange.ColumnPosition convert(String position) {
+
+    if (position == null || position.isEmpty()) {
+      return TableChange.ColumnPosition.defaultPos();
+    } else if (position.equalsIgnoreCase("first")) {
+      return TableChange.ColumnPosition.first();
+    } else {
+      return TableChange.ColumnPosition.after(position);
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
index 546c92e33..c6164107b 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TestableCommandLine.java
@@ -21,6 +21,7 @@
 package org.apache.gravitino.cli;
 
 import java.util.Map;
+import org.apache.gravitino.cli.commands.AddColumn;
 import org.apache.gravitino.cli.commands.AddRoleToGroup;
 import org.apache.gravitino.cli.commands.AddRoleToUser;
 import org.apache.gravitino.cli.commands.CatalogAudit;
@@ -36,6 +37,7 @@ import org.apache.gravitino.cli.commands.CreateTag;
 import org.apache.gravitino.cli.commands.CreateTopic;
 import org.apache.gravitino.cli.commands.CreateUser;
 import org.apache.gravitino.cli.commands.DeleteCatalog;
+import org.apache.gravitino.cli.commands.DeleteColumn;
 import org.apache.gravitino.cli.commands.DeleteFileset;
 import org.apache.gravitino.cli.commands.DeleteGroup;
 import org.apache.gravitino.cli.commands.DeleteMetalake;
@@ -101,6 +103,13 @@ import org.apache.gravitino.cli.commands.TopicDetails;
 import org.apache.gravitino.cli.commands.UntagEntity;
 import org.apache.gravitino.cli.commands.UpdateCatalogComment;
 import org.apache.gravitino.cli.commands.UpdateCatalogName;
+import org.apache.gravitino.cli.commands.UpdateColumnAutoIncrement;
+import org.apache.gravitino.cli.commands.UpdateColumnComment;
+import org.apache.gravitino.cli.commands.UpdateColumnDatatype;
+import org.apache.gravitino.cli.commands.UpdateColumnDefault;
+import org.apache.gravitino.cli.commands.UpdateColumnName;
+import org.apache.gravitino.cli.commands.UpdateColumnNullability;
+import org.apache.gravitino.cli.commands.UpdateColumnPosition;
 import org.apache.gravitino.cli.commands.UpdateFilesetComment;
 import org.apache.gravitino.cli.commands.UpdateFilesetName;
 import org.apache.gravitino.cli.commands.UpdateMetalakeComment;
@@ -669,4 +678,135 @@ public class TestableCommandLine {
       String property) {
     return new RemoveFilesetProperty(url, ignore, metalake, catalog, schema, 
fileset, property);
   }
+
+  protected AddColumn newAddColumn(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String datatype,
+      String comment,
+      String position,
+      boolean nullable,
+      boolean autoIncrement,
+      String defaultValue) {
+    return new AddColumn(
+        url,
+        ignore,
+        metalake,
+        catalog,
+        schema,
+        table,
+        column,
+        datatype,
+        comment,
+        position,
+        nullable,
+        autoIncrement,
+        defaultValue);
+  }
+
+  protected DeleteColumn newDeleteColumn(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column) {
+    return new DeleteColumn(url, ignore, metalake, catalog, schema, table, 
column);
+  }
+
+  protected UpdateColumnComment newUpdateColumnComment(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String comment) {
+    return new UpdateColumnComment(url, ignore, metalake, catalog, schema, 
table, column, comment);
+  }
+
+  protected UpdateColumnName newUpdateColumnName(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String rename) {
+    return new UpdateColumnName(url, ignore, metalake, catalog, schema, table, 
column, rename);
+  }
+
+  protected UpdateColumnDatatype newUpdateColumnDatatype(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String datatype) {
+    return new UpdateColumnDatatype(
+        url, ignore, metalake, catalog, schema, table, column, datatype);
+  }
+
+  protected UpdateColumnPosition newUpdateColumnPosition(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String position) {
+    return new UpdateColumnPosition(
+        url, ignore, metalake, catalog, schema, table, column, position);
+  }
+
+  protected UpdateColumnNullability newUpdateColumnNullability(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      boolean nullable) {
+    return new UpdateColumnNullability(
+        url, ignore, metalake, catalog, schema, table, column, nullable);
+  }
+
+  protected UpdateColumnAutoIncrement newUpdateColumnAutoIncrement(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      boolean autoIncrement) {
+    return new UpdateColumnAutoIncrement(
+        url, ignore, metalake, catalog, schema, table, column, autoIncrement);
+  }
+
+  protected UpdateColumnDefault newUpdateColumnDefault(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String table,
+      String column,
+      String defaultValue,
+      String dataType) {
+    return new UpdateColumnDefault(
+        url, ignore, metalake, catalog, schema, table, column, defaultValue, 
dataType);
+  }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/TypeConverter.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/TypeConverter.java
new file mode 100644
index 000000000..c34560c2a
--- /dev/null
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/TypeConverter.java
@@ -0,0 +1,121 @@
+/*
+ * 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 org.apache.gravitino.rel.types.Type;
+import org.apache.gravitino.rel.types.Types;
+
+public class TypeConverter {
+
+  /**
+   * Converts a type name string to the appropriate Type.
+   *
+   * @param typeName The string representing the data type (e.g., "boolean", 
"byte", "string").
+   * @return An instance of the appropriate Type.
+   * @throws IllegalArgumentException if the type name is not recognized.
+   */
+  public static Type convert(String typeName) {
+    switch (typeName.toLowerCase()) {
+      case "null":
+        return Types.NullType.get();
+      case "boolean":
+        return Types.BooleanType.get();
+      case "byte":
+        return Types.ByteType.get();
+      case "ubyte":
+        return Types.ByteType.unsigned();
+      case "short":
+        return Types.ShortType.get();
+      case "ushort":
+        return Types.ShortType.unsigned();
+      case "integer":
+        return Types.IntegerType.get();
+      case "uinteger":
+        return Types.IntegerType.unsigned();
+      case "long":
+        return Types.LongType.get();
+      case "ulong":
+        return Types.LongType.unsigned();
+      case "float":
+        return Types.FloatType.get();
+      case "double":
+        return Types.DoubleType.get();
+      case "date":
+        return Types.DateType.get();
+      case "time":
+        return Types.TimeType.get();
+      case "timestamp":
+        return Types.TimestampType.withoutTimeZone();
+      case "tztimestamp":
+        return Types.TimestampType.withTimeZone();
+      case "intervalyear":
+        return Types.IntervalYearType.get();
+      case "intervalday":
+        return Types.IntervalDayType.get();
+      case "uuid":
+        return Types.UUIDType.get();
+      case "string":
+        return Types.StringType.get();
+      case "binary":
+        return Types.BinaryType.get();
+      default:
+        throw new IllegalArgumentException("Unknown or unsupported type: " + 
typeName);
+    }
+  }
+
+  /**
+   * Converts a type name string to the appropriate Type.
+   *
+   * @param typeName The string representing the data type (e.g., "fixed" or 
"varchar").
+   * @param length Length of the data type.
+   * @return An instance of the appropriate Type.
+   * @throws IllegalArgumentException if the type name is not recognized.
+   */
+  public static Type convert(String typeName, int length) {
+    if (typeName.toLowerCase().startsWith("fixed")) {
+      return Types.FixedType.of(length);
+    } else if (typeName.toLowerCase().startsWith("varchar")) {
+      return Types.VarCharType.of(length);
+    } else if (typeName.toLowerCase().startsWith("char")) {
+      return Types.FixedCharType.of(length);
+    } else {
+      throw new IllegalArgumentException(
+          "Unknown or unsupported variable length type: " + typeName);
+    }
+  }
+
+  /**
+   * Converts a type name string to the appropriate Type.
+   *
+   * @param typeName The string representing the data type. Only "decimal" is 
supported.
+   * @param precision Precision of the decimal.
+   * @param scale Scale of the decimal.
+   * @return An instance of the appropriate Type.
+   * @throws IllegalArgumentException if the type name is not recognized.
+   */
+  public static Type convert(String typeName, int precision, int scale) {
+    if (typeName.toLowerCase().startsWith("decimal")) {
+      return Types.DecimalType.of(precision, scale);
+    } else {
+      throw new IllegalArgumentException(
+          "Unknown or unsupported precision and scale type: " + typeName);
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AddColumn.java
similarity index 58%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/AddColumn.java
index ff0cfa760..39dc6eb9d 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/AddColumn.java
@@ -20,25 +20,34 @@
 package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.DefaultConverter;
 import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.ParseType;
+import org.apache.gravitino.cli.PositionConverter;
 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.TableChange;
+import org.apache.gravitino.rel.types.Type;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+public class AddColumn extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
+  protected final String column;
+  protected final String datatype;
   protected final String comment;
+  protected final String position;
+  protected final boolean nullable;
+  protected final boolean autoIncrement;
+  protected final String defaultValue;
 
   /**
-   * Update the comment of a table.
+   * Adds an optional column to a table.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +55,62 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the new column.
+   * @param datatype The data type of the new column.
+   * @param comment The comment for the column (optional).
+   * @param position The position of the column (optional).
+   * @param nullable True if the column can be null, false if it cannot be 
(optional).
+   * @param autoIncrement True if the column auto increments (optional).
+   * @param defaultValue Default value of the column (optional).
    */
-  public UpdateTableComment(
+  public AddColumn(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      String datatype,
+      String comment,
+      String position,
+      boolean nullable,
+      boolean autoIncrement,
+      String defaultValue) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
+    this.column = column;
+    this.datatype = datatype;
     this.comment = comment;
+    this.position = position;
+    this.nullable = nullable;
+    this.autoIncrement = autoIncrement;
+    this.defaultValue = defaultValue;
   }
 
-  /** Update the comment of a table. */
+  /** Adds an optional column to a table. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+    Type convertedDatatype = ParseType.toType(datatype);
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      NameIdentifier name = NameIdentifier.of(schema, table);
+      TableChange change =
+          TableChange.addColumn(
+              columns,
+              convertedDatatype,
+              comment,
+              PositionConverter.convert(position),
+              nullable,
+              autoIncrement,
+              DefaultConverter.convert(defaultValue, datatype));
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(name, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -89,6 +128,6 @@ public class UpdateTableComment extends Command {
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " added to table " + table + ".");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteColumn.java
similarity index 78%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteColumn.java
index ff0cfa760..2e2bbd3f9 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteColumn.java
@@ -23,22 +23,22 @@ 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.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+public class DeleteColumn extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
 
   /**
-   * Update the comment of a table.
+   * Delete a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +46,34 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
    */
-  public UpdateTableComment(
+  public DeleteColumn(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
   }
 
-  /** Update the comment of a table. */
+  /** Delete a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
-
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      TableChange change = TableChange.deleteColumn(columns, false);
+      NameIdentifier name = NameIdentifier.of(schema, table);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(name, change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +86,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " deleted.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnAutoIncrement.java
similarity index 70%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnAutoIncrement.java
index ff0cfa760..f848e6a68 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnAutoIncrement.java
@@ -23,22 +23,24 @@ 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.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the auto increment of a column. */
+public class UpdateColumnAutoIncrement extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
+  protected final boolean autoincrement;
 
   /**
-   * Update the comment of a table.
+   * Update the auto increment of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +48,38 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param autoincrement True if the column can be null, false if it must 
have non-null values.
    */
-  public UpdateTableComment(
+  public UpdateColumnAutoIncrement(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      boolean autoincrement) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
+    this.autoincrement = autoincrement;
   }
 
-  /** Update the comment of a table. */
+  /** Update the auto increment of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      TableChange change = TableChange.updateColumnAutoIncrement(columns, 
autoincrement);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +92,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " auto increment changed to " + autoincrement 
+ ".");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnComment.java
similarity index 76%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnComment.java
index ff0cfa760..09d73ccb9 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnComment.java
@@ -23,22 +23,24 @@ 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.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the comment of a column. */
+public class UpdateColumnComment extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
+  protected final String column;
   protected final String comment;
 
   /**
-   * Update the comment of a table.
+   * Update the comment of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +48,38 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param comment New column comment.
    */
-  public UpdateTableComment(
+  public UpdateColumnComment(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
+      String column,
       String comment) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
+    this.column = column;
     this.comment = comment;
   }
 
-  /** Update the comment of a table. */
+  /** Update the comment of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      TableChange change = TableChange.updateColumnComment(columns, comment);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +92,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " comment changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDatatype.java
similarity index 70%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDatatype.java
index ff0cfa760..6a40be953 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDatatype.java
@@ -21,24 +21,28 @@ package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.ParseType;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
+import org.apache.gravitino.rel.types.Type;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the data type of a column. */
+public class UpdateColumnDatatype extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
+  protected final String datatype;
 
   /**
-   * Update the comment of a table.
+   * Update the data type of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +50,39 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param datatype The new data type name.
    */
-  public UpdateTableComment(
+  public UpdateColumnDatatype(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      String datatype) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
+    this.datatype = datatype;
   }
 
-  /** Update the comment of a table. */
+  /** Update the data type of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+    Type convertedDatatype = ParseType.toType(datatype);
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      TableChange change = TableChange.updateColumnType(columns, 
convertedDatatype);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +95,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " datatype changed to " + datatype + ".");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDefault.java
similarity index 68%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDefault.java
index ff0cfa760..de5a00c1f 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnDefault.java
@@ -20,25 +20,29 @@
 package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.DefaultConverter;
 import org.apache.gravitino.cli.ErrorMessages;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the default type of a column. */
+public class UpdateColumnDefault extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
+  protected final String defaultValue;
+  protected final String dataType;
 
   /**
-   * Update the comment of a table.
+   * Update the default type of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +50,43 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param defaultValue The new default value.
+   * @param dataType The new default data type.
    */
-  public UpdateTableComment(
+  public UpdateColumnDefault(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      String defaultValue,
+      String dataType) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
+    this.defaultValue = defaultValue;
+    this.dataType = dataType;
   }
 
-  /** Update the comment of a table. */
+  /** Update the default type of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      TableChange change =
+          TableChange.updateColumnDefaultValue(
+              columns, DefaultConverter.convert(defaultValue, dataType));
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +99,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " default changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnName.java
similarity index 76%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnName.java
index e2f4fae61..f03d0ce8d 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnName.java
@@ -23,22 +23,24 @@ 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.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the name of a table. */
-public class UpdateTableName extends Command {
+/** Update the name of a column. */
+public class UpdateColumnName extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
+  protected final String column;
   protected final String name;
 
   /**
-   * Update the name of a table.
+   * Update the name of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,15 +48,17 @@ public class UpdateTableName extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param name The new metalake name.
+   * @param column The name of the column.
+   * @param name The new column name.
    */
-  public UpdateTableName(
+  public UpdateColumnName(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
+      String column,
       String name) {
     super(url, ignoreVersions);
     this.metalake = metalake;
@@ -62,16 +66,20 @@ public class UpdateTableName extends Command {
     this.schema = schema;
     this.table = table;
     this.name = name;
+    this.column = column;
   }
 
-  /** Update the name of a table. */
+  /** Update the name of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.rename(name);
+      TableChange change = TableChange.renameColumn(columns, name);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +92,14 @@ public class UpdateTableName extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " name changed.");
+    System.out.println(column + " name changed.");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnNullability.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnNullability.java
index ff0cfa760..88f3634e0 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnNullability.java
@@ -23,22 +23,24 @@ 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.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the nullability of a column. */
+public class UpdateColumnNullability extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
+  protected final boolean nullability;
 
   /**
-   * Update the comment of a table.
+   * Update the nullability of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +48,38 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param nullability True if the column can be null, false if it must have 
non-null values.
    */
-  public UpdateTableComment(
+  public UpdateColumnNullability(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      boolean nullability) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
+    this.nullability = nullability;
   }
 
-  /** Update the comment of a table. */
+  /** Update the nullability of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
+      TableChange change = TableChange.updateColumnNullability(columns, 
nullability);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +92,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " nullability changed to " + nullability + 
".");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnPosition.java
similarity index 71%
copy from 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
copy to 
clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnPosition.java
index ff0cfa760..460fe6ed1 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateColumnPosition.java
@@ -21,24 +21,27 @@ package org.apache.gravitino.cli.commands;
 
 import org.apache.gravitino.NameIdentifier;
 import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.cli.PositionConverter;
 import org.apache.gravitino.client.GravitinoClient;
 import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchColumnException;
 import org.apache.gravitino.exceptions.NoSuchMetalakeException;
 import org.apache.gravitino.exceptions.NoSuchSchemaException;
 import org.apache.gravitino.exceptions.NoSuchTableException;
 import org.apache.gravitino.rel.TableChange;
 
-/** Update the comment of a table. */
-public class UpdateTableComment extends Command {
+/** Update the position of a column. */
+public class UpdateColumnPosition extends Command {
 
   protected final String metalake;
   protected final String catalog;
   protected final String schema;
   protected final String table;
-  protected final String comment;
+  protected final String column;
+  protected final String position;
 
   /**
-   * Update the comment of a table.
+   * Update the position of a column.
    *
    * @param url The URL of the Gravitino server.
    * @param ignoreVersions If true don't check the client/server versions 
match.
@@ -46,32 +49,40 @@ public class UpdateTableComment extends Command {
    * @param catalog The name of the catalog.
    * @param schema The name of the schema.
    * @param table The name of the table.
-   * @param comment New metalake comment.
+   * @param column The name of the column.
+   * @param position The new position of the column.
    */
-  public UpdateTableComment(
+  public UpdateColumnPosition(
       String url,
       boolean ignoreVersions,
       String metalake,
       String catalog,
       String schema,
       String table,
-      String comment) {
+      String column,
+      String position) {
     super(url, ignoreVersions);
     this.metalake = metalake;
     this.catalog = catalog;
     this.schema = schema;
     this.table = table;
-    this.comment = comment;
+    this.column = column;
+    this.position = position;
   }
 
-  /** Update the comment of a table. */
+  /** Update the position of a column. */
+  @Override
   public void handle() {
+    String[] columns = {column};
+
     try {
-      NameIdentifier tableName = NameIdentifier.of(schema, table);
+      NameIdentifier columnName = NameIdentifier.of(schema, table);
       GravitinoClient client = buildClient(metalake);
-      TableChange change = TableChange.updateComment(comment);
 
-      client.loadCatalog(catalog).asTableCatalog().alterTable(tableName, 
change);
+      TableChange change =
+          TableChange.updateColumnPosition(columns, 
PositionConverter.convert(position));
+
+      client.loadCatalog(catalog).asTableCatalog().alterTable(columnName, 
change);
     } catch (NoSuchMetalakeException err) {
       System.err.println(ErrorMessages.UNKNOWN_METALAKE);
       return;
@@ -84,11 +95,14 @@ public class UpdateTableComment extends Command {
     } catch (NoSuchTableException err) {
       System.err.println(ErrorMessages.UNKNOWN_TABLE);
       return;
+    } catch (NoSuchColumnException err) {
+      System.err.println(ErrorMessages.UNKNOWN_COLUMN);
+      return;
     } catch (Exception exp) {
       System.err.println(exp.getMessage());
       return;
     }
 
-    System.out.println(table + " comment changed.");
+    System.out.println(column + " position changed to " + position + ".");
   }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
index ff0cfa760..3edd73940 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableComment.java
@@ -65,6 +65,7 @@ public class UpdateTableComment extends Command {
   }
 
   /** Update the comment of a table. */
+  @Override
   public void handle() {
     try {
       NameIdentifier tableName = NameIdentifier.of(schema, table);
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
index e2f4fae61..2346ee4ab 100644
--- 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/UpdateTableName.java
@@ -65,6 +65,7 @@ public class UpdateTableName extends Command {
   }
 
   /** Update the name of a table. */
+  @Override
   public void handle() {
     try {
       NameIdentifier tableName = NameIdentifier.of(schema, table);
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java
index b7e091ebd..647e85353 100644
--- a/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestColumnCommands.java
@@ -27,7 +27,16 @@ import static org.mockito.Mockito.when;
 
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.AddColumn;
+import org.apache.gravitino.cli.commands.DeleteColumn;
 import org.apache.gravitino.cli.commands.ListColumns;
+import org.apache.gravitino.cli.commands.UpdateColumnAutoIncrement;
+import org.apache.gravitino.cli.commands.UpdateColumnComment;
+import org.apache.gravitino.cli.commands.UpdateColumnDatatype;
+import org.apache.gravitino.cli.commands.UpdateColumnDefault;
+import org.apache.gravitino.cli.commands.UpdateColumnName;
+import org.apache.gravitino.cli.commands.UpdateColumnNullability;
+import org.apache.gravitino.cli.commands.UpdateColumnPosition;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
@@ -60,4 +69,296 @@ class TestColumnCommands {
     commandLine.handleCommandLine();
     verify(mockList).handle();
   }
+
+  @Test
+  void testAddColumn() {
+    AddColumn mockAddColumn = mock(AddColumn.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    
when(mockCommandLine.hasOption(GravitinoOptions.DATATYPE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.DATATYPE)).thenReturn("varchar(100)");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.POSITION)).thenReturn(null);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.DEFAULT)).thenReturn(null);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.CREATE));
+    doReturn(mockAddColumn)
+        .when(commandLine)
+        .newAddColumn(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "varchar(100)",
+            "comment",
+            null,
+            true,
+            false,
+            null);
+    commandLine.handleCommandLine();
+    verify(mockAddColumn).handle();
+  }
+
+  @Test
+  void testDeleteColumn() {
+    DeleteColumn mockDelete = mock(DeleteColumn.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteColumn(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testUpdateColumnComment() {
+    UpdateColumnComment mockUpdateColumn = mock(UpdateColumnComment.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("new 
comment");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateColumn)
+        .when(commandLine)
+        .newUpdateColumnComment(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "new comment");
+    commandLine.handleCommandLine();
+    verify(mockUpdateColumn).handle();
+  }
+
+  @Test
+  void testUpdateColumnName() {
+    UpdateColumnName mockUpdateName = mock(UpdateColumnName.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    when(mockCommandLine.hasOption(GravitinoOptions.RENAME)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.RENAME)).thenReturn("renamed");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateName)
+        .when(commandLine)
+        .newUpdateColumnName(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "renamed");
+    commandLine.handleCommandLine();
+    verify(mockUpdateName).handle();
+  }
+
+  @Test
+  void testUpdateColumnDatatype() {
+    UpdateColumnDatatype mockUpdateDatatype = mock(UpdateColumnDatatype.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    
when(mockCommandLine.hasOption(GravitinoOptions.DATATYPE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.DATATYPE)).thenReturn("varchar(250)");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateDatatype)
+        .when(commandLine)
+        .newUpdateColumnDatatype(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "varchar(250)");
+    commandLine.handleCommandLine();
+    verify(mockUpdateDatatype).handle();
+  }
+
+  @Test
+  void testUpdateColumnPosition() {
+    UpdateColumnPosition mockUpdatePosition = mock(UpdateColumnPosition.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    
when(mockCommandLine.hasOption(GravitinoOptions.POSITION)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.POSITION)).thenReturn("first");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdatePosition)
+        .when(commandLine)
+        .newUpdateColumnPosition(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "first");
+    commandLine.handleCommandLine();
+    verify(mockUpdatePosition).handle();
+  }
+
+  @Test
+  void testUpdateColumnNullability() {
+    UpdateColumnNullability mockUpdateNull = 
mock(UpdateColumnNullability.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.NULL)).thenReturn("true");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateNull)
+        .when(commandLine)
+        .newUpdateColumnNullability(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            true);
+    commandLine.handleCommandLine();
+    verify(mockUpdateNull).handle();
+  }
+
+  @Test
+  void testUpdateColumnAutoIncrement() {
+    UpdateColumnAutoIncrement mockUpdateAuto = 
mock(UpdateColumnAutoIncrement.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.AUTO)).thenReturn("true");
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateAuto)
+        .when(commandLine)
+        .newUpdateColumnAutoIncrement(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            true);
+    commandLine.handleCommandLine();
+    verify(mockUpdateAuto).handle();
+  }
+
+  @Test
+  void testUpdateColumnDefault() {
+    UpdateColumnDefault mockUpdateDefault = mock(UpdateColumnDefault.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(CommandEntities.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.users.name");
+    when(mockCommandLine.hasOption(GravitinoOptions.DEFAULT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.DEFAULT)).thenReturn("Fred 
Smith");
+    
when(mockCommandLine.hasOption(GravitinoOptions.DATATYPE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.DATATYPE)).thenReturn("varchar(100)");
+
+    when(mockCommandLine.hasOption(GravitinoOptions.NULL)).thenReturn(false);
+    when(mockCommandLine.hasOption(GravitinoOptions.AUTO)).thenReturn(false);
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.COLUMN, 
CommandActions.UPDATE));
+    doReturn(mockUpdateDefault)
+        .when(commandLine)
+        .newUpdateColumnDefault(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "users",
+            "name",
+            "Fred Smith",
+            "varchar(100)");
+    commandLine.handleCommandLine();
+    verify(mockUpdateDefault).handle();
+  }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.java
new file mode 100644
index 000000000..7aa20bd37
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestDefaultConverter.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 static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.expressions.literals.Literals;
+import org.junit.jupiter.api.Test;
+
+class TestDefaultConverter {
+
+  @Test
+  void testConvertNullDefaultValue() {
+    String defaultValue = null;
+    String dataType = "INTEGER";
+    Expression result = DefaultConverter.convert(defaultValue, dataType);
+
+    assertEquals(
+        Column.DEFAULT_VALUE_NOT_SET,
+        result,
+        "Expected DEFAULT_VALUE_NOT_SET for null defaultValue.");
+  }
+
+  @Test
+  void testConvertEmptyDefaultValue() {
+    String defaultValue = "";
+    String dataType = "INTEGER";
+    Expression result = DefaultConverter.convert(defaultValue, dataType);
+
+    assertEquals(
+        Column.DEFAULT_VALUE_NOT_SET,
+        result,
+        "Expected DEFAULT_VALUE_NOT_SET for empty defaultValue.");
+  }
+
+  @Test
+  void testConvertCurrentTimestamp() {
+    String defaultValue = "current_timestamp";
+    String dataType = "TIMESTAMP";
+    Expression result = DefaultConverter.convert(defaultValue, dataType);
+
+    assertEquals(
+        Column.DEFAULT_VALUE_OF_CURRENT_TIMESTAMP,
+        result,
+        "Expected DEFAULT_VALUE_OF_CURRENT_TIMESTAMP for 'current_timestamp' 
defaultValue.");
+  }
+
+  @Test
+  void testConvertLiteralValue() {
+    String defaultValue = "42";
+    String dataType = "INTEGER";
+    Expression result = DefaultConverter.convert(defaultValue, dataType);
+    Expression expected = Literals.of(defaultValue, 
ParseType.toType(dataType));
+
+    assertEquals(expected, result, "Expected literal expression for 
non-special default value.");
+  }
+}
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 05e78b4c8..ecde923a3 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
@@ -43,7 +43,7 @@ public class TestFulllName {
 
   @Test
   public void entityFromFullNameOption() throws Exception {
-    String[] args = {"--metalake", "metalakeA", "--name", 
"catalogB.schemaC.tableD"};
+    String[] args = {"--metalake", "metalakeA", "--name", 
"catalogB.schemaC.tableD.columnE"};
     CommandLine commandLine = new DefaultParser().parse(options, args);
     FullName fullName = new FullName(commandLine);
 
@@ -55,6 +55,8 @@ public class TestFulllName {
     assertEquals("schemaC", schemaName);
     String tableName = fullName.getTableName();
     assertEquals("tableD", tableName);
+    String columnName = fullName.getColumnName();
+    assertEquals("columnE", columnName);
   }
 
   @Test
@@ -100,6 +102,7 @@ public class TestFulllName {
     assertFalse(fullName.hasCatalogName());
     assertFalse(fullName.hasSchemaName());
     assertFalse(fullName.hasTableName());
+    assertFalse(fullName.hasColumnName());
   }
 
   @Test
@@ -110,6 +113,7 @@ public class TestFulllName {
     assertTrue(fullName.hasCatalogName());
     assertFalse(fullName.hasSchemaName());
     assertFalse(fullName.hasTableName());
+    assertFalse(fullName.hasColumnName());
   }
 
   @Test
@@ -120,6 +124,7 @@ public class TestFulllName {
     assertTrue(fullName.hasCatalogName());
     assertTrue(fullName.hasSchemaName());
     assertFalse(fullName.hasTableName());
+    assertFalse(fullName.hasColumnName());
   }
 
   @Test
@@ -132,5 +137,19 @@ public class TestFulllName {
     assertTrue(fullName.hasCatalogName());
     assertTrue(fullName.hasSchemaName());
     assertTrue(fullName.hasTableName());
+    assertFalse(fullName.hasColumnName());
+  }
+
+  @Test
+  public void hasPartNameColumn() throws Exception {
+    String[] args = {
+      "table", "details", "--metalake", "metalake", "--name", 
"catalog.schema.table.column"
+    };
+    CommandLine commandLine = new DefaultParser().parse(options, args);
+    FullName fullName = new FullName(commandLine);
+    assertTrue(fullName.hasCatalogName());
+    assertTrue(fullName.hasSchemaName());
+    assertTrue(fullName.hasTableName());
+    assertTrue(fullName.hasColumnName());
   }
 }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestParseType.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestParseType.java
new file mode 100644
index 000000000..c53d3c2bd
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestParseType.java
@@ -0,0 +1,67 @@
+/*
+ * 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.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+public class TestParseType {
+
+  @Test
+  public void testParseVarcharWithLength() {
+    ParsedType parsed = ParseType.parse("varchar(10)");
+    assertNotNull(parsed);
+    assertEquals("varchar", parsed.getTypeName());
+    assertEquals(10, parsed.getLength());
+    assertNull(parsed.getScale());
+    assertNull(parsed.getPrecision());
+  }
+
+  @Test
+  public void testParseDecimalWithPrecisionAndScale() {
+    ParsedType parsed = ParseType.parse("decimal(10,5)");
+    assertNotNull(parsed);
+    assertEquals("decimal", parsed.getTypeName());
+    assertEquals(10, parsed.getPrecision());
+    assertEquals(5, parsed.getScale());
+    assertNull(parsed.getLength());
+  }
+
+  @Test
+  public void testParseIntegerWithoutParameters() {
+    ParsedType parsed = ParseType.parse("int()");
+    assertNull(parsed); // Expect null because the format is unsupported
+  }
+
+  @Test
+  public void testParseOrdinaryInput() {
+    assertNull(ParseType.parse("string"));
+    assertNull(ParseType.parse("int"));
+  }
+
+  @Test
+  public void testParseMalformedInput() {
+    assertNull(ParseType.parse("varchar(-10)"));
+    assertNull(ParseType.parse("decimal(10,abc)"));
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestPositionConverter.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestPositionConverter.java
new file mode 100644
index 000000000..1fc55097e
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestPositionConverter.java
@@ -0,0 +1,82 @@
+/*
+ * 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.assertNotNull;
+
+import org.apache.gravitino.rel.TableChange;
+import org.junit.jupiter.api.Test;
+
+public class TestPositionConverter {
+
+  @Test
+  public void testConvertFirstPosition() {
+    TableChange.ColumnPosition position = PositionConverter.convert("first");
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.first(), position);
+  }
+
+  @Test
+  public void testConvertNullPosition() {
+    TableChange.ColumnPosition position = PositionConverter.convert(null);
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.defaultPos(), position);
+  }
+
+  @Test
+  public void testConvertEmptyPosition() {
+    TableChange.ColumnPosition position = PositionConverter.convert("");
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.defaultPos(), position);
+  }
+
+  @Test
+  public void testConvertValidColumnName() {
+    String columnName = "column1";
+    TableChange.ColumnPosition position = 
PositionConverter.convert(columnName);
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.after(columnName), position);
+  }
+
+  @Test
+  public void testConvertCaseInsensitiveColumnName() {
+    String columnName = "COLUMN2";
+    TableChange.ColumnPosition position = 
PositionConverter.convert(columnName);
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.after(columnName), position);
+  }
+
+  @Test
+  public void testConvertWhitespaceColumnName() {
+    String columnName = "   column3   ";
+    TableChange.ColumnPosition position = 
PositionConverter.convert(columnName.trim());
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.after("column3"), position);
+  }
+
+  @Test
+  public void testConvertUnrecognizedPosition() {
+    String unrecognized = "unrecognized";
+    TableChange.ColumnPosition position = 
PositionConverter.convert(unrecognized);
+    assertNotNull(position);
+    assertEquals(TableChange.ColumnPosition.after(unrecognized), position);
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestTypeConverter.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTypeConverter.java
new file mode 100644
index 000000000..a4145b294
--- /dev/null
+++ b/clients/cli/src/test/java/org/apache/gravitino/cli/TestTypeConverter.java
@@ -0,0 +1,99 @@
+/*
+ * 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.assertThrows;
+
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Test;
+
+public class TestTypeConverter {
+
+  @Test
+  public void testConvertBasicTypes() {
+    assertEquals(Types.NullType.get(), TypeConverter.convert("null"));
+    assertEquals(Types.BooleanType.get(), TypeConverter.convert("boolean"));
+    assertEquals(Types.ByteType.get(), TypeConverter.convert("byte"));
+    assertEquals(Types.ByteType.unsigned(), TypeConverter.convert("ubyte"));
+    assertEquals(Types.ShortType.get(), TypeConverter.convert("short"));
+    assertEquals(Types.ShortType.unsigned(), TypeConverter.convert("ushort"));
+    assertEquals(Types.IntegerType.get(), TypeConverter.convert("integer"));
+    assertEquals(Types.IntegerType.unsigned(), 
TypeConverter.convert("uinteger"));
+    assertEquals(Types.LongType.get(), TypeConverter.convert("long"));
+    assertEquals(Types.LongType.unsigned(), TypeConverter.convert("ulong"));
+    assertEquals(Types.FloatType.get(), TypeConverter.convert("float"));
+    assertEquals(Types.DoubleType.get(), TypeConverter.convert("double"));
+    assertEquals(Types.DateType.get(), TypeConverter.convert("date"));
+    assertEquals(Types.TimeType.get(), TypeConverter.convert("time"));
+    assertEquals(Types.TimestampType.withoutTimeZone(), 
TypeConverter.convert("timestamp"));
+    assertEquals(Types.TimestampType.withTimeZone(), 
TypeConverter.convert("tztimestamp"));
+    assertEquals(Types.IntervalYearType.get(), 
TypeConverter.convert("intervalyear"));
+    assertEquals(Types.IntervalDayType.get(), 
TypeConverter.convert("intervalday"));
+    assertEquals(Types.UUIDType.get(), TypeConverter.convert("uuid"));
+    assertEquals(Types.StringType.get(), TypeConverter.convert("string"));
+    assertEquals(Types.BinaryType.get(), TypeConverter.convert("binary"));
+  }
+
+  @Test
+  public void testConvertBasicTypesCaseInsensitive() {
+    assertEquals(Types.BooleanType.get(), TypeConverter.convert("BOOLEAN"));
+    assertEquals(Types.StringType.get(), TypeConverter.convert("STRING"));
+  }
+
+  @Test
+  public void testConvertUnsupportedType() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> {
+          TypeConverter.convert("unsupportedType");
+        });
+  }
+
+  @Test
+  public void testConvertFixedVarcharAndChar() {
+    assertEquals(Types.FixedType.of(10), TypeConverter.convert("fixed", 10));
+    assertEquals(Types.VarCharType.of(20), TypeConverter.convert("varchar", 
20));
+    assertEquals(Types.FixedCharType.of(30), TypeConverter.convert("char", 
30));
+  }
+
+  @Test
+  public void testConvertFixedVarcharAndCharUnsupportedType() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> {
+          TypeConverter.convert("unsupportedType", 10);
+        });
+  }
+
+  @Test
+  public void testConvertDecimal() {
+    assertEquals(Types.DecimalType.of(10, 5), TypeConverter.convert("decimal", 
10, 5));
+  }
+
+  @Test
+  public void testConvertDecimalUnsupportedType() {
+    assertThrows(
+        IllegalArgumentException.class,
+        () -> {
+          TypeConverter.convert("unsupportedType", 10, 5);
+        });
+  }
+}
diff --git a/docs/cli.md b/docs/cli.md
index ff4f94ab9..211e16bd5 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -29,10 +29,13 @@ The general structure for running commands with the 
Gravitino CLI is `gcli entit
  ```bash
   usage: gcli 
[metalake|catalog|schema|table|column|user|group|tag|topic|fileset] 
[list|details|create|delete|update|set|remove|properties|revoke|grant] [options]
   Options
- [options]
+ usage: gcli
  -a,--audit              display audit information
+    --auto <arg>         column value auto-increments (true/false)
  -c,--comment <arg>      entity comment
  -d,--distribution       display distribution information
+    --datatype <arg>     column data type
+    --default <arg>      default column value
  -f,--force              force operation
  -g,--group <arg>        group name
  -h,--help               command help information
@@ -40,10 +43,13 @@ The general structure for running commands with the 
Gravitino CLI is `gcli entit
  -l,--user <arg>         user name
  -m,--metalake <arg>     metalake name
  -n,--name <arg>         full entity name (dot separated)
+    --null <arg>         column value can be null (true/false)
  -o,--owner              display entity owner
+    --output <arg>       output format (plain/table)
  -P,--property <arg>     property name
  -p,--properties <arg>   property name/value pairs
     --partition          display partition information
+    --position <arg>     position of column
  -r,--role <arg>         role name
     --rename <arg>       new entity name
  -s,--server             Gravitino server version
@@ -721,3 +727,36 @@ gcli fileset set  --name hadoop.schema.fileset --property 
test --value value
 ```bash
 gcli fileset remove --name hadoop.schema.fileset --property test
 ```
+
+### column commands
+
+Note that some commands are not supported depending on what the database 
supports.
+
+When setting the datatype of a column the following basic types are currently 
supported:
+null, boolean, byte, ubyte, short, ushort, integer, uinteger, long, ulong, 
float, double, date, time, timestamp, tztimestamp, intervalyear, intervalday, 
uuid, string, binary
+
+In addition decimal(precision,scale) and varchar(length).
+
+#### Add a column
+
+```bash
+gcli column create --name catalog_postgres.hr.departments.value --datatype long
+gcli column create --name catalog_postgres.hr.departments.money --datatype 
"decimal(10,2)"
+gcli column create --name catalog_postgres.hr.departments.name --datatype 
"varchar(100)"
+gcli column create --name catalog_postgres.hr.departments.fullname --datatype 
"varchar(250)" --default "Fred Smith" --null=false
+```
+
+#### Delete a column
+
+```bash
+gcli  column delete --name catalog_postgres.hr.departments.money
+```
+
+#### Update a column
+
+```bash
+gcli column update --name catalog_postgres.hr.departments.value --rename values
+gcli column update --name catalog_postgres.hr.departments.values --datatype 
"varchar(500)"
+gcli column update --name catalog_postgres.hr.departments.values --position 
name
+gcli column update --name catalog_postgres.hr.departments.name --null=true
+```
\ No newline at end of file

Reply via email to