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 7a42199a5 [#5379] Add basic Fileset commands to Gravitno CLI (#5380)
7a42199a5 is described below

commit 7a42199a5fcaa02eaa8053cfec5b6f7b3dab3850
Author: Justin Mclean <[email protected]>
AuthorDate: Fri Nov 29 13:20:46 2024 +1100

    [#5379] Add basic Fileset commands to Gravitno CLI (#5380)
    
    ### What changes were proposed in this pull request?
    
    Added basic Fileset commands to Gravitno CLI
    
    ### Why are the changes needed?
    
    To expand Gravitino CLI support.
    
    Fix: #5379
    
    ### Does this PR introduce _any_ user-facing change?
    
    No, but it adds extra commands to the Gravitino CLI.
    
    ### How was this patch tested?
    
    Compiled and tested locally.
---
 .../org/apache/gravitino/cli/CommandEntities.java  |   2 +
 .../org/apache/gravitino/cli/ErrorMessages.java    |   2 +
 .../java/org/apache/gravitino/cli/FullName.java    |  15 +-
 .../apache/gravitino/cli/GravitinoCommandLine.java |  29 ++++
 .../org/apache/gravitino/cli/GravitinoOptions.java |   6 +-
 .../java/org/apache/gravitino/cli/Properties.java  |  18 ++-
 .../apache/gravitino/cli/TestableCommandLine.java  |  37 +++++
 .../gravitino/cli/commands/CreateFileset.java      | 103 ++++++++++++
 .../gravitino/cli/commands/DeleteFileset.java      | 102 ++++++++++++
 .../gravitino/cli/commands/FilesetDetails.java     | 101 ++++++++++++
 .../gravitino/cli/commands/ListFilesets.java       |  82 ++++++++++
 .../apache/gravitino/cli/TestCommandEntities.java  |   2 +
 .../apache/gravitino/cli/TestFilesetCommands.java  | 175 +++++++++++++++++++++
 docs/cli.md                                        |  32 +++-
 14 files changed, 691 insertions(+), 15 deletions(-)

diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java
index a68703a13..aaf10e8e1 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/CommandEntities.java
@@ -34,6 +34,7 @@ public class CommandEntities {
   public static final String USER = "user";
   public static final String GROUP = "group";
   public static final String TAG = "tag";
+  public static final String FILESET = "fileset";
   public static final String ROLE = "role";
 
   private static final HashSet<String> VALID_ENTITIES = new HashSet<>();
@@ -47,6 +48,7 @@ public class CommandEntities {
     VALID_ENTITIES.add(USER);
     VALID_ENTITIES.add(GROUP);
     VALID_ENTITIES.add(TAG);
+    VALID_ENTITIES.add(FILESET);
     VALID_ENTITIES.add(ROLE);
   }
 
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 3c7870968..873ded785 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_FILESET = "Unknown fileset.";
+  public static final String FILESET_EXISTS = "Fileset already exists.";
   public static final String TAG_EMPTY = "Error: Must configure --tag option.";
   public static final String UNKNOWN_ROLE = "Unknown role.";
   public static final String ROLE_EXISTS = "Role already exists.";
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/FullName.java
index 48b68bb44..3c53401ca 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
@@ -80,7 +80,7 @@ public class FullName {
   }
 
   /**
-   * Retrieves the catalog name from the second part of the full name option.
+   * Retrieves the catalog name from the first part of the full name option.
    *
    * @return The catalog name, or null if not found.
    */
@@ -89,7 +89,7 @@ public class FullName {
   }
 
   /**
-   * Retrieves the schema name from the third part of the full name option.
+   * Retrieves the schema name from the second part of the full name option.
    *
    * @return The schema name, or null if not found.
    */
@@ -98,7 +98,7 @@ public class FullName {
   }
 
   /**
-   * Retrieves the table name from the fourth part of the full name option.
+   * Retrieves the table name from the third part of the full name option.
    *
    * @return The table name, or null if not found.
    */
@@ -106,6 +106,15 @@ public class FullName {
     return getNamePart(2);
   }
 
+  /**
+   * Retrieves the fileset name from the third part of the full name option.
+   *
+   * @return The table name, or null if not found.
+   */
+  public String getFilesetName() {
+    return getNamePart(2);
+  }
+
   /**
    * Helper method to retrieve a specific part of the full name based on the 
position of the part.
    *
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 a4b41b12e..cc2147c02 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
@@ -124,6 +124,8 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
       handleCatalogCommand();
     } else if (entity.equals(CommandEntities.METALAKE)) {
       handleMetalakeCommand();
+    } else if (entity.equals(CommandEntities.FILESET)) {
+      handleFilesetCommand();
     } else if (entity.equals(CommandEntities.USER)) {
       handleUserCommand();
     } else if (entity.equals(CommandEntities.GROUP)) {
@@ -489,6 +491,33 @@ public class GravitinoCommandLine extends 
TestableCommandLine {
     }
   }
 
+  /**
+   * Handles the command execution for filesets based on command type and the 
command line options.
+   */
+  private void handleFilesetCommand() {
+    String url = getUrl();
+    FullName name = new FullName(line);
+    String metalake = name.getMetalakeName();
+    String catalog = name.getCatalogName();
+    String schema = name.getSchemaName();
+    String fileset = name.getFilesetName();
+
+    if (CommandActions.DETAILS.equals(command)) {
+      newFilesetDetails(url, ignore, metalake, catalog, schema, 
fileset).handle();
+    } else if (CommandActions.LIST.equals(command)) {
+      newListFilesets(url, ignore, metalake, catalog, schema).handle();
+    } else if (CommandActions.CREATE.equals(command)) {
+      String comment = line.getOptionValue(GravitinoOptions.COMMENT);
+      String[] properties = line.getOptionValues(GravitinoOptions.PROPERTIES);
+      Map<String, String> propertyMap = new Properties().parse(properties);
+      newCreateFileset(url, ignore, metalake, catalog, schema, fileset, 
comment, propertyMap)
+          .handle();
+    } else if (CommandActions.DELETE.equals(command)) {
+      boolean force = line.hasOption(GravitinoOptions.FORCE);
+      newDeleteFileset(url, ignore, force, metalake, catalog, schema, 
fileset).handle();
+    }
+  }
+
   /**
    * Retrieves the Gravitinno URL from the command line options or the 
GRAVITINO_URL environment
    * variable or the Gravitio config file.
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 dd9fedd7a..ee3e09472 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
@@ -65,9 +65,9 @@ public class GravitinoOptions {
     options.addOption(createArgOption("m", METALAKE, "metalake name"));
     options.addOption(createSimpleOption("i", IGNORE, "ignore client/sever 
version check"));
     options.addOption(createSimpleOption("a", AUDIT, "display audit 
information"));
-    options.addOption(createSimpleOption("x", INDEX, "Display index 
infromation"));
-    options.addOption(createSimpleOption("d", DISTRIBUTION, "Display 
distribution information"));
-    options.addOption(createSimpleOption(null, PARTITION, "Display partition 
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"));
 
     // Create/update options
     options.addOption(createArgOption(null, RENAME, "new entity name"));
diff --git a/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java
index 1c166102f..16213976c 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/Properties.java
@@ -61,14 +61,16 @@ public class Properties {
   public Map<String, String> parse(String[] inputs) {
     HashMap<String, String> map = new HashMap<>();
 
-    for (String input : inputs) {
-      // Split the input by the delimiter into key-value pairs
-      String[] pairs = input.split(delimiter);
-      for (String pair : pairs) {
-        // Split each key-value pair by the separator
-        String[] keyValue = pair.split(keyValueSeparator, 2);
-        if (keyValue.length == 2) {
-          map.put(keyValue[0].trim(), keyValue[1].trim());
+    if (inputs != null) {
+      for (String input : inputs) {
+        // Split the input by the delimiter into key-value pairs
+        String[] pairs = input.split(delimiter);
+        for (String pair : pairs) {
+          // Split each key-value pair by the separator
+          String[] keyValue = pair.split(keyValueSeparator, 2);
+          if (keyValue.length == 2) {
+            map.put(keyValue[0].trim(), keyValue[1].trim());
+          }
         }
       }
     }
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 94435160d..1df28658b 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
@@ -27,6 +27,7 @@ import org.apache.gravitino.cli.commands.CatalogAudit;
 import org.apache.gravitino.cli.commands.CatalogDetails;
 import org.apache.gravitino.cli.commands.ClientVersion;
 import org.apache.gravitino.cli.commands.CreateCatalog;
+import org.apache.gravitino.cli.commands.CreateFileset;
 import org.apache.gravitino.cli.commands.CreateGroup;
 import org.apache.gravitino.cli.commands.CreateMetalake;
 import org.apache.gravitino.cli.commands.CreateRole;
@@ -34,6 +35,7 @@ import org.apache.gravitino.cli.commands.CreateSchema;
 import org.apache.gravitino.cli.commands.CreateTag;
 import org.apache.gravitino.cli.commands.CreateUser;
 import org.apache.gravitino.cli.commands.DeleteCatalog;
+import org.apache.gravitino.cli.commands.DeleteFileset;
 import org.apache.gravitino.cli.commands.DeleteGroup;
 import org.apache.gravitino.cli.commands.DeleteMetalake;
 import org.apache.gravitino.cli.commands.DeleteRole;
@@ -41,12 +43,14 @@ import org.apache.gravitino.cli.commands.DeleteSchema;
 import org.apache.gravitino.cli.commands.DeleteTable;
 import org.apache.gravitino.cli.commands.DeleteTag;
 import org.apache.gravitino.cli.commands.DeleteUser;
+import org.apache.gravitino.cli.commands.FilesetDetails;
 import org.apache.gravitino.cli.commands.GroupDetails;
 import org.apache.gravitino.cli.commands.ListAllTags;
 import org.apache.gravitino.cli.commands.ListCatalogProperties;
 import org.apache.gravitino.cli.commands.ListCatalogs;
 import org.apache.gravitino.cli.commands.ListColumns;
 import org.apache.gravitino.cli.commands.ListEntityTags;
+import org.apache.gravitino.cli.commands.ListFilesets;
 import org.apache.gravitino.cli.commands.ListGroups;
 import org.apache.gravitino.cli.commands.ListIndexes;
 import org.apache.gravitino.cli.commands.ListMetalakeProperties;
@@ -442,4 +446,37 @@ public class TestableCommandLine {
       String url, boolean ignore, String metalake, String entity, String 
entityType) {
     return new OwnerDetails(url, ignore, metalake, entity, entityType);
   }
+
+  protected FilesetDetails newFilesetDetails(
+      String url, boolean ignore, String metalake, String catalog, String 
schema, String fileset) {
+    return new FilesetDetails(url, ignore, metalake, catalog, schema, fileset);
+  }
+
+  protected ListFilesets newListFilesets(
+      String url, boolean ignore, String metalake, String catalog, String 
schema) {
+    return new ListFilesets(url, ignore, metalake, catalog, schema);
+  }
+
+  protected CreateFileset newCreateFileset(
+      String url,
+      boolean ignore,
+      String metalake,
+      String catalog,
+      String schema,
+      String fileset,
+      String comment,
+      Map<String, String> propertyMap) {
+    return new CreateFileset(url, ignore, metalake, catalog, schema, fileset, 
comment, propertyMap);
+  }
+
+  protected DeleteFileset newDeleteFileset(
+      String url,
+      boolean ignore,
+      boolean force,
+      String metalake,
+      String catalog,
+      String schema,
+      String fileset) {
+    return new DeleteFileset(url, ignore, force, metalake, catalog, schema, 
fileset);
+  }
 }
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateFileset.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateFileset.java
new file mode 100644
index 000000000..bc109fcc5
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/CreateFileset.java
@@ -0,0 +1,103 @@
+/*
+ * 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.commands;
+
+import java.util.Map;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.FilesetAlreadyExistsException;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.file.Fileset;
+
+public class CreateFileset extends Command {
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+  protected final String fileset;
+  protected final String comment;
+  protected final Map<String, String> properties;
+
+  /**
+   * Create a new fileset.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   * @param metalake The name of the metalake.
+   * @param catalog The name of the catalog.
+   * @param schema The name of the schema.
+   * @param fileset The name of the fileset.
+   * @param comment The fileset's comment.
+   * @param properties The catalog's properties.
+   */
+  public CreateFileset(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String fileset,
+      String comment,
+      Map<String, String> properties) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+    this.fileset = fileset;
+    this.comment = comment;
+    this.properties = properties;
+  }
+
+  /** Create a new fileset. */
+  @Override
+  public void handle() {
+    NameIdentifier name = NameIdentifier.of(schema, fileset);
+    boolean managed = properties.get("managed").equals("true");
+    String location = properties.get("location");
+    Fileset.Type filesetType = managed ? Fileset.Type.MANAGED : 
Fileset.Type.EXTERNAL;
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      client
+          .loadCatalog(catalog)
+          .asFilesetCatalog()
+          .createFileset(name, comment, filesetType, location, null);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (FilesetAlreadyExistsException err) {
+      System.err.println(ErrorMessages.FILESET_EXISTS);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    System.out.println(fileset + " created");
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteFileset.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteFileset.java
new file mode 100644
index 000000000..bc76dcb26
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/DeleteFileset.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.gravitino.cli.commands;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.AreYouSure;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchFilesetException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+
+public class DeleteFileset extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+  protected final String fileset;
+  protected final boolean force;
+
+  /**
+   * Delete a fileset.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   * @param force Force operation.
+   * @param metalake The name of the metalake.
+   * @param catalog The name of the catalog.
+   * @param schema The name of the schema.
+   * @param fileset The name of the fileset.
+   */
+  public DeleteFileset(
+      String url,
+      boolean ignoreVersions,
+      boolean force,
+      String metalake,
+      String catalog,
+      String schema,
+      String fileset) {
+    super(url, ignoreVersions);
+    this.force = force;
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+    this.fileset = fileset;
+  }
+
+  /** Delete a fileset. */
+  @Override
+  public void handle() {
+    NameIdentifier name = NameIdentifier.of(schema, fileset);
+    boolean deleted = false;
+
+    if (!AreYouSure.really(force)) {
+      return;
+    }
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      deleted = 
client.loadCatalog(catalog).asFilesetCatalog().dropFileset(name);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (NoSuchFilesetException err) {
+      System.err.println(ErrorMessages.UNKNOWN_FILESET);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    if (deleted) {
+      System.out.println(fileset + " deleted.");
+    } else {
+      System.out.println(fileset + " not deleted.");
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/FilesetDetails.java
 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/FilesetDetails.java
new file mode 100644
index 000000000..8d7a5d2f3
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/FilesetDetails.java
@@ -0,0 +1,101 @@
+/*
+ * 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.commands;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchFilesetException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.file.Fileset;
+
+/** Displays the details of fileset. */
+public class FilesetDetails extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+  protected final String fileset;
+
+  /**
+   * Displays the details of a fileset.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   * @param metalake The name of the metalake.
+   * @param catalog The name of the catalog.
+   * @param schema The name of the schenma.
+   * @param fileset The name of the fileset.
+   */
+  public FilesetDetails(
+      String url,
+      boolean ignoreVersions,
+      String metalake,
+      String catalog,
+      String schema,
+      String fileset) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+    this.fileset = fileset;
+  }
+
+  /** Displays the name and comments of fileset. */
+  @Override
+  public void handle() {
+    NameIdentifier name = NameIdentifier.of(schema, fileset);
+    Fileset result = null;
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      result = 
client.loadCatalog(catalog).asFilesetCatalog().loadFileset(name);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (NoSuchFilesetException err) {
+      System.err.println(ErrorMessages.UNKNOWN_FILESET);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    if (result != null) {
+      String filesetType = (result.type() == Fileset.Type.MANAGED) ? "managed" 
: "external";
+      System.out.println(
+          result.name()
+              + ","
+              + filesetType
+              + ","
+              + result.comment()
+              + ","
+              + result.storageLocation());
+    }
+  }
+}
diff --git 
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListFilesets.java 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListFilesets.java
new file mode 100644
index 000000000..428fe9bb1
--- /dev/null
+++ 
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListFilesets.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.commands;
+
+import com.google.common.base.Joiner;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.cli.ErrorMessages;
+import org.apache.gravitino.client.GravitinoClient;
+import org.apache.gravitino.exceptions.NoSuchCatalogException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+
+/** List all fileset names in a schema. */
+public class ListFilesets extends Command {
+
+  protected final String metalake;
+  protected final String catalog;
+  protected final String schema;
+
+  /**
+   * Lists all filesets in a schema.
+   *
+   * @param url The URL of the Gravitino server.
+   * @param ignoreVersions If true don't check the client/server versions 
match.
+   * @param metalake The name of the metalake.
+   * @param catalog The name of the catalog.
+   * @param schema The name of the schema.
+   */
+  public ListFilesets(
+      String url, boolean ignoreVersions, String metalake, String catalog, 
String schema) {
+    super(url, ignoreVersions);
+    this.metalake = metalake;
+    this.catalog = catalog;
+    this.schema = schema;
+  }
+
+  /** List all filesets names in a schema. */
+  @Override
+  public void handle() {
+    Namespace name = Namespace.of(schema);
+    NameIdentifier[] filesets = new NameIdentifier[0];
+
+    try {
+      GravitinoClient client = buildClient(metalake);
+      filesets = 
client.loadCatalog(catalog).asFilesetCatalog().listFilesets(name);
+    } catch (NoSuchMetalakeException err) {
+      System.err.println(ErrorMessages.UNKNOWN_METALAKE);
+      return;
+    } catch (NoSuchCatalogException err) {
+      System.err.println(ErrorMessages.UNKNOWN_CATALOG);
+      return;
+    } catch (NoSuchSchemaException err) {
+      System.err.println(ErrorMessages.UNKNOWN_SCHEMA);
+      return;
+    } catch (Exception exp) {
+      System.err.println(exp.getMessage());
+      return;
+    }
+
+    String all = Joiner.on(",").join(filesets);
+
+    System.out.println(all.toString());
+  }
+}
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
index 50743c1f3..5f60d3e7e 100644
--- 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestCommandEntities.java
@@ -38,6 +38,8 @@ public class TestCommandEntities {
     assertTrue(
         CommandEntities.isValidEntity(CommandEntities.TABLE), "TABLE should be 
a valid entity");
     assertTrue(CommandEntities.isValidEntity(CommandEntities.TAG), "TAG should 
be a valid entity");
+    assertTrue(
+        CommandEntities.isValidEntity(CommandEntities.FILESET), "FILESET 
should be a valid entity");
     assertTrue(
         CommandEntities.isValidEntity(CommandEntities.ROLE), "ROLE should be a 
valid entity");
   }
diff --git 
a/clients/cli/src/test/java/org/apache/gravitino/cli/TestFilesetCommands.java 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFilesetCommands.java
new file mode 100644
index 000000000..d7932bde8
--- /dev/null
+++ 
b/clients/cli/src/test/java/org/apache/gravitino/cli/TestFilesetCommands.java
@@ -0,0 +1,175 @@
+/*
+ * 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.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.gravitino.cli.commands.CreateFileset;
+import org.apache.gravitino.cli.commands.DeleteFileset;
+import org.apache.gravitino.cli.commands.FilesetDetails;
+import org.apache.gravitino.cli.commands.ListFilesets;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class TestFilesetCommands {
+  private CommandLine mockCommandLine;
+  private Options mockOptions;
+
+  @BeforeEach
+  void setUp() {
+    mockCommandLine = mock(CommandLine.class);
+    mockOptions = mock(Options.class);
+  }
+
+  @Test
+  void testListFilesetsCommand() {
+    ListFilesets mockList = mock(ListFilesets.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");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.FILESET, 
CommandActions.LIST));
+    doReturn(mockList)
+        .when(commandLine)
+        .newListFilesets(
+            GravitinoCommandLine.DEFAULT_URL, false, "metalake_demo", 
"catalog", "schema");
+    commandLine.handleCommandLine();
+    verify(mockList).handle();
+  }
+
+  @Test
+  void testFilesetDetailsCommand() {
+    FilesetDetails mockDetails = mock(FilesetDetails.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.fileset");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.FILESET, 
CommandActions.DETAILS));
+    doReturn(mockDetails)
+        .when(commandLine)
+        .newFilesetDetails(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "fileset");
+    commandLine.handleCommandLine();
+    verify(mockDetails).handle();
+  }
+
+  @Test
+  void testCreateFilesetCommand() {
+    CreateFileset mockCreate = mock(CreateFileset.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.fileset");
+    when(mockCommandLine.hasOption(GravitinoOptions.COMMENT)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.COMMENT)).thenReturn("comment");
+
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.FILESET, 
CommandActions.CREATE));
+    doReturn(mockCreate)
+        .when(commandLine)
+        .newCreateFileset(
+            eq(GravitinoCommandLine.DEFAULT_URL),
+            eq(false),
+            eq("metalake_demo"),
+            eq("catalog"),
+            eq("schema"),
+            eq("fileset"),
+            eq("comment"),
+            any());
+    commandLine.handleCommandLine();
+    verify(mockCreate).handle();
+  }
+
+  @Test
+  void testDeleteFilesetCommand() {
+    DeleteFileset mockDelete = mock(DeleteFileset.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.fileset");
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.FILESET, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteFileset(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            false,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "fileset");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+
+  @Test
+  void testDeleteFilesetForceCommand() {
+    DeleteFileset mockDelete = mock(DeleteFileset.class);
+    
when(mockCommandLine.hasOption(GravitinoOptions.METALAKE)).thenReturn(true);
+    
when(mockCommandLine.getOptionValue(GravitinoOptions.METALAKE)).thenReturn("metalake_demo");
+    when(mockCommandLine.hasOption(GravitinoOptions.NAME)).thenReturn(true);
+    when(mockCommandLine.getOptionValue(GravitinoOptions.NAME))
+        .thenReturn("catalog.schema.fileset");
+    when(mockCommandLine.hasOption(GravitinoOptions.FORCE)).thenReturn(true);
+    GravitinoCommandLine commandLine =
+        spy(
+            new GravitinoCommandLine(
+                mockCommandLine, mockOptions, CommandEntities.FILESET, 
CommandActions.DELETE));
+    doReturn(mockDelete)
+        .when(commandLine)
+        .newDeleteFileset(
+            GravitinoCommandLine.DEFAULT_URL,
+            false,
+            true,
+            "metalake_demo",
+            "catalog",
+            "schema",
+            "fileset");
+    commandLine.handleCommandLine();
+    verify(mockDelete).handle();
+  }
+}
diff --git a/docs/cli.md b/docs/cli.md
index dbbbca7cb..856e591a4 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -28,10 +28,11 @@ The general structure for running commands with the 
Gravitino CLI is `gcli entit
 
  ```bash
  [options]
- usage: gcli [metalake|catalog|schema|table|column|user|group|tag] 
[list|details|create|delete|update|set|remove|properties|revoke|grant] [options]
+ usage: gcli [metalake|catalog|schema|table|column|user|group|tag|fileset] 
[list|details|create|delete|update|set|remove|properties|revoke|grant] [options]
  Options
  -a,--audit              display audit information
  -c,--comment <arg>      entity comment
+ -d,--distribution       display distribution information
  -f,--force              force operation
  -g,--group <arg>        group name
  -h,--help               command help information
@@ -39,8 +40,10 @@ 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)
+ -o,--owner              entity owner
  -P,--property <arg>     property name
  -p,--properties <arg>   property name/value pairs
+    --partition          display partition information
  -r,--role <arg>         role name
     --rename <arg>       new entity name
  -s,--server             Gravitino server version
@@ -48,6 +51,7 @@ The general structure for running commands with the Gravitino 
CLI is `gcli entit
  -u,--url <arg>          Gravitino URL (default: http://localhost:8090)
  -v,--version            Gravitino client version
  -V,--value <arg>        property value
+ -x,--index              display index information
  -z,--provider <arg>     provider one of hadoop, hive, mysql, postgres,
                          iceberg, kafka
  ```
@@ -591,3 +595,29 @@ gcli group grant --group groupA --role admin
 ```bash
 gcli group revoke  --group groupA --role admin
 ```
+
+### Fileset commands
+
+#### Create a fileset
+
+```bash
+gcli fileset create --name hadoop.schema.fileset --properties 
managed=true,location=file:/tmp/root/schema/example
+```
+
+#### List filesets
+
+```bash
+gcli fileset list --name hadoop.schema
+```
+
+#### Display a fileset's details
+
+```bash
+gcli fileset details --name hadoop.schema.fileset
+```
+
+#### Delete a fileset
+
+```bash
+gcli fileset delete --name hadoop.schema.fileset
+```


Reply via email to