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

SpriCoder pushed a commit to branch fs/inner-view
in repository https://gitbox.apache.org/repos/asf/iotdb.git

commit 33a3a30d7637d94015032a6d1b71a100819a77df
Author: spricoder <[email protected]>
AuthorDate: Sat May 2 20:27:37 2026 +0800

    add unix and extend
---
 .../2026-05-02-cli-fs-unix-standard-extensions.md  | 55 ++++++++++++++++++++++
 .../org/apache/iotdb/cli/fs/FilesystemShell.java   | 37 +++++++++++++--
 .../iotdb/cli/fs/command/FilesystemCommand.java    |  2 +
 .../cli/fs/command/FilesystemCommandParser.java    | 25 ++++++++++
 .../fs/provider/FilesystemMutationProvider.java    |  6 +++
 .../provider/TableFilesystemMutationProvider.java  | 43 +++++++++++++++++
 .../UnsupportedFilesystemMutationProvider.java     | 15 ++++++
 .../apache/iotdb/cli/fs/FilesystemShellTest.java   | 44 +++++++++++++++++
 .../fs/command/FilesystemCommandParserTest.java    | 36 ++++++++++++++
 .../TableFilesystemMutationProviderTest.java       | 38 +++++++++++++++
 10 files changed, 297 insertions(+), 4 deletions(-)

diff --git 
a/docs/superpowers/plans/2026-05-02-cli-fs-unix-standard-extensions.md 
b/docs/superpowers/plans/2026-05-02-cli-fs-unix-standard-extensions.md
new file mode 100644
index 00000000000..635667782fa
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-02-cli-fs-unix-standard-extensions.md
@@ -0,0 +1,55 @@
+# CLI Filesystem Unix Standard Extensions Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans 
to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for 
tracking.
+
+**Goal:** Extend CLI filesystem mode with a first slice of standard Unix-style 
database operations.
+
+**Architecture:** Keep command parsing in `FilesystemCommandParser`, command 
dispatch in `FilesystemShell`, and database mutations behind 
`FilesystemMutationProvider`. Table-mode mutations map to SQL through 
`TableFilesystemMutationProvider`; tree-mode writes remain unsupported through 
`UnsupportedFilesystemMutationProvider`.
+
+**Tech Stack:** Java, JUnit 4, Mockito, Maven module `iotdb-client/cli`.
+
+---
+
+### Task 1: Add Standard Command Parsing
+
+**Files:**
+- Modify: 
`iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParserTest.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommand.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParser.java`
+
+- [ ] Write failing tests for `rmdir <path>`, `rm -r <path>`, `cp <source> 
<target>`, and `ls -R [path]`.
+- [ ] Run `mvn -pl iotdb-client/cli -Dtest=FilesystemCommandParserTest test` 
and verify the new tests fail because commands are not implemented.
+- [ ] Implement the minimal parser changes.
+- [ ] Re-run the parser test and verify it passes.
+
+### Task 2: Add Shell Dispatch
+
+**Files:**
+- Modify: 
`iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/FilesystemShellTest.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/FilesystemShell.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java`
+
+- [ ] Write failing shell tests proving `rmdir`, `rm -r`, and `cp` call the 
mutation provider only when writes are enabled, and `ls -R` recursively lists 
children.
+- [ ] Run `mvn -pl iotdb-client/cli -Dtest=FilesystemShellTest test` and 
verify the tests fail for missing behavior.
+- [ ] Implement minimal shell dispatch and provider interface methods.
+- [ ] Re-run the shell test and verify it passes.
+
+### Task 3: Add Table Mutation SQL
+
+**Files:**
+- Modify: 
`iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.java`
+- Modify: 
`iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java`
+
+- [ ] Write failing provider tests for dropping a database through 
`rmdir`/recursive remove and copying `/db/t1.schema` to `/db/t2.schema`.
+- [ ] Run `mvn -pl iotdb-client/cli -Dtest=TableFilesystemMutationProviderTest 
test` and verify failures.
+- [ ] Implement minimal table mutation SQL: `DROP DATABASE <db>` and `CREATE 
TABLE <target> LIKE <source>`.
+- [ ] Re-run provider tests and verify they pass.
+
+### Task 4: Regression Verification
+
+**Files:**
+- Existing fs-mode tests.
+
+- [ ] Run `mvn -pl iotdb-client/cli 
-Dtest=FilesystemCommandParserTest,FilesystemShellTest,TableFilesystemMutationProviderTest,CliFilesystemModeTest
 test`.
+- [ ] Fix only failures caused by this change.
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/FilesystemShell.java 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/FilesystemShell.java
index 407744fc03c..508a0847050 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/FilesystemShell.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/FilesystemShell.java
@@ -54,8 +54,8 @@ public class FilesystemShell {
   private static final List<String> COMMANDS =
       Arrays.asList(
           "pwd", "ls", "ll", "cd", "stat", "cat", "head", "tail", "wc", 
"grep", "find", "less",
-          "more", "file", "du", "mkdir", "rm", "mv", "cut", "paste", "join", 
"tree", "help", "exit",
-          "quit", "tee");
+          "more", "file", "du", "mkdir", "rmdir", "rm", "mv", "cp", "cut", 
"paste", "join", "tree",
+          "help", "exit", "quit", "tee");
 
   private final CliContext ctx;
   private final FilesystemSchemaProvider provider;
@@ -127,12 +127,18 @@ public class FilesystemShell {
       case MKDIR:
         mkdir(command.getPath());
         return true;
+      case RMDIR:
+        rmdir(command.getPath());
+        return true;
       case RM:
-        remove(command.getPath());
+        remove(command.getPath(), command.getOption());
         return true;
       case MV:
         move(command.getPaths());
         return true;
+      case CP:
+        copy(command.getPaths());
+        return true;
       case CUT:
         printCut(command.getPath(), command.getOption(), command.getPattern());
         return true;
@@ -432,11 +438,23 @@ public class FilesystemShell {
     mutationProvider.mkdir(resolvedPath);
   }
 
-  private void remove(String path) throws SQLException {
+  private void rmdir(String path) throws SQLException {
+    FsPath resolvedPath = resolve(path);
+    if (!ensureWritable("rmdir", resolvedPath)) {
+      return;
+    }
+    mutationProvider.rmdir(resolvedPath);
+  }
+
+  private void remove(String path, String option) throws SQLException {
     FsPath resolvedPath = resolve(path);
     if (!ensureWritable("rm", resolvedPath)) {
       return;
     }
+    if ("-r".equals(option)) {
+      mutationProvider.removeRecursive(resolvedPath);
+      return;
+    }
     mutationProvider.remove(resolvedPath);
   }
 
@@ -449,6 +467,15 @@ public class FilesystemShell {
     mutationProvider.move(source, target);
   }
 
+  private void copy(List<String> paths) throws SQLException {
+    FsPath source = resolve(paths.get(0));
+    FsPath target = resolve(paths.get(1));
+    if (!ensureWritable("cp", source)) {
+      return;
+    }
+    mutationProvider.copy(source, target);
+  }
+
   private void append(String path, boolean nonInteractive) throws SQLException 
{
     FsPath resolvedPath = resolve(path);
     if (!ensureWritable("tee", resolvedPath)) {
@@ -549,8 +576,10 @@ public class FilesystemShell {
     ctx.getPrinter().println("file <path>");
     ctx.getPrinter().println("du <path>");
     ctx.getPrinter().println("mkdir <path>");
+    ctx.getPrinter().println("rmdir <path>");
     ctx.getPrinter().println("rm <path>");
     ctx.getPrinter().println("mv <source> <target>");
+    ctx.getPrinter().println("cp <source> <target>");
     ctx.getPrinter().println("cut -d<delimiter> -f<fields> <path>");
     ctx.getPrinter().println("paste <path>...");
     ctx.getPrinter().println("join [-t delimiter] [-1 field] [-2 field] 
<path1> <path2>");
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommand.java
 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommand.java
index 7d379236a78..14a6f19f814 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommand.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommand.java
@@ -41,8 +41,10 @@ public class FilesystemCommand {
     FILE,
     DU,
     MKDIR,
+    RMDIR,
     RM,
     MV,
+    CP,
     CUT,
     PASTE,
     JOIN,
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParser.java
 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParser.java
index 565b9bad58f..8b1aea2c1f1 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParser.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParser.java
@@ -100,12 +100,18 @@ public class FilesystemCommandParser {
     if ("mkdir".equals(command)) {
       return FilesystemCommand.path(FilesystemCommand.Type.MKDIR, 
pathArgument(tokens));
     }
+    if ("rmdir".equals(command)) {
+      return FilesystemCommand.path(FilesystemCommand.Type.RMDIR, 
pathArgument(tokens));
+    }
     if ("rm".equals(command)) {
       return parseRm(tokens);
     }
     if ("mv".equals(command)) {
       return parseMv(tokens);
     }
+    if ("cp".equals(command)) {
+      return parseCp(tokens);
+    }
     if ("cut".equals(command)) {
       return parseCut(tokens);
     }
@@ -255,6 +261,9 @@ public class FilesystemCommandParser {
       return FilesystemCommand.invalid("Missing rm path");
     }
     if (tokens[1].startsWith("-")) {
+      if ("-r".equals(tokens[1]) && tokens.length >= 3) {
+        return FilesystemCommand.option(FilesystemCommand.Type.RM, "-r", 
tokens[2]);
+      }
       return FilesystemCommand.invalid("Unsupported rm option: " + tokens[1]);
     }
     return FilesystemCommand.path(FilesystemCommand.Type.RM, tokens[1]);
@@ -270,10 +279,21 @@ public class FilesystemCommandParser {
     return FilesystemCommand.paths(FilesystemCommand.Type.MV, paths);
   }
 
+  private static FilesystemCommand parseCp(String[] tokens) {
+    if (tokens.length < 3) {
+      return FilesystemCommand.invalid("Usage: cp <source> <target>");
+    }
+    List<String> paths = new ArrayList<>();
+    paths.add(tokens[1]);
+    paths.add(tokens[2]);
+    return FilesystemCommand.paths(FilesystemCommand.Type.CP, paths);
+  }
+
   private static FilesystemCommand parseList(String[] tokens, boolean 
longMode) {
     FilesystemCommand.Type type = longMode ? FilesystemCommand.Type.LL : 
FilesystemCommand.Type.LS;
     String path = DEFAULT_PATH;
     boolean all = false;
+    boolean recursive = false;
 
     for (int i = 1; i < tokens.length; i++) {
       String token = tokens[i];
@@ -284,6 +304,8 @@ public class FilesystemCommandParser {
             type = FilesystemCommand.Type.LL;
           } else if (option == 'a') {
             all = true;
+          } else if (option == 'R') {
+            recursive = true;
           } else {
             return FilesystemCommand.invalid("Unsupported ls option: -" + 
option);
           }
@@ -292,6 +314,9 @@ public class FilesystemCommandParser {
         path = token;
       }
     }
+    if (recursive) {
+      return FilesystemCommand.tree(path, DEFAULT_TREE_DEPTH);
+    }
     return FilesystemCommand.option(type, all ? "-a" : "", path);
   }
 
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java
 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java
index 3d1d3afbd88..81c7df6beeb 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java
@@ -28,9 +28,15 @@ public interface FilesystemMutationProvider {
 
   void mkdir(FsPath path) throws SQLException;
 
+  void rmdir(FsPath path) throws SQLException;
+
   void remove(FsPath path) throws SQLException;
 
+  void removeRecursive(FsPath path) throws SQLException;
+
   void move(FsPath source, FsPath target) throws SQLException;
 
+  void copy(FsPath source, FsPath target) throws SQLException;
+
   void append(FsPath path, List<String> lines) throws SQLException;
 }
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java
 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java
index 710ff02a609..e4642be8999 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java
@@ -30,6 +30,7 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
   private static final String INVALID_WRITE_OPERATION =
       "Invalid filesystem write operation for this path";
   private static final String CSV_SUFFIX = ".csv";
+  private static final String SCHEMA_SUFFIX = ".schema";
 
   private final SqlExecutor executor;
 
@@ -45,6 +46,11 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
     executor.execute("CREATE DATABASE " + 
TableFilesystemSql.identifier(path.getFileName()));
   }
 
+  @Override
+  public void rmdir(FsPath path) throws SQLException {
+    dropDatabase(path);
+  }
+
   @Override
   public void remove(FsPath path) throws SQLException {
     if (!isDataFile(path)) {
@@ -53,6 +59,11 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
     executor.execute("DROP TABLE " + toTablePath(path));
   }
 
+  @Override
+  public void removeRecursive(FsPath path) throws SQLException {
+    dropDatabase(path);
+  }
+
   @Override
   public void move(FsPath source, FsPath target) throws SQLException {
     if (!isDataFile(source) || !isDataFile(target)) {
@@ -68,6 +79,18 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
             + TableFilesystemSql.identifier(tableName(target)));
   }
 
+  @Override
+  public void copy(FsPath source, FsPath target) throws SQLException {
+    if (!isSchemaFile(source) || !isSchemaFile(target)) {
+      throw invalidOperation();
+    }
+    executor.execute(
+        "CREATE TABLE "
+            + toTablePath(target, SCHEMA_SUFFIX)
+            + " LIKE "
+            + toTablePath(source, SCHEMA_SUFFIX));
+  }
+
   @Override
   public void append(FsPath path, List<String> lines) throws SQLException {
     if (!isDataFile(path)) {
@@ -91,10 +114,21 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
     return new SQLException(INVALID_WRITE_OPERATION);
   }
 
+  private void dropDatabase(FsPath path) throws SQLException {
+    if (path.getSegments().size() != 1) {
+      throw invalidOperation();
+    }
+    executor.execute("DROP DATABASE " + 
TableFilesystemSql.identifier(path.getFileName()));
+  }
+
   private static String toTablePath(FsPath path) {
     return TableFilesystemSql.tablePath(databaseName(path), tableName(path));
   }
 
+  private static String toTablePath(FsPath path, String suffix) {
+    return TableFilesystemSql.tablePath(databaseName(path), tableName(path, 
suffix));
+  }
+
   private static String databaseName(FsPath path) {
     return path.getSegments().get(0);
   }
@@ -103,11 +137,20 @@ public class TableFilesystemMutationProvider implements 
FilesystemMutationProvid
     return path.getSegments().size() == 2 && 
path.getFileName().endsWith(CSV_SUFFIX);
   }
 
+  private static boolean isSchemaFile(FsPath path) {
+    return path.getSegments().size() == 2 && 
path.getFileName().endsWith(SCHEMA_SUFFIX);
+  }
+
   private static String tableName(FsPath path) {
     String fileName = path.getFileName();
     return fileName.substring(0, fileName.length() - CSV_SUFFIX.length());
   }
 
+  private static String tableName(FsPath path, String suffix) {
+    String fileName = path.getFileName();
+    return fileName.substring(0, fileName.length() - suffix.length());
+  }
+
   private static FsPath parent(FsPath path) {
     List<String> segments = path.getSegments();
     StringBuilder builder = new StringBuilder("/");
diff --git 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java
 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java
index 2d061c32e4e..9b3cf6b3353 100644
--- 
a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java
+++ 
b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java
@@ -33,16 +33,31 @@ public class UnsupportedFilesystemMutationProvider 
implements FilesystemMutation
     throw unsupported();
   }
 
+  @Override
+  public void rmdir(FsPath path) throws SQLException {
+    throw unsupported();
+  }
+
   @Override
   public void remove(FsPath path) throws SQLException {
     throw unsupported();
   }
 
+  @Override
+  public void removeRecursive(FsPath path) throws SQLException {
+    throw unsupported();
+  }
+
   @Override
   public void move(FsPath source, FsPath target) throws SQLException {
     throw unsupported();
   }
 
+  @Override
+  public void copy(FsPath source, FsPath target) throws SQLException {
+    throw unsupported();
+  }
+
   @Override
   public void append(FsPath path, List<String> lines) throws SQLException {
     throw unsupported();
diff --git 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/FilesystemShellTest.java
 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/FilesystemShellTest.java
index 6cb435ede80..78e9e88516c 100644
--- 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/FilesystemShellTest.java
+++ 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/FilesystemShellTest.java
@@ -247,6 +247,28 @@ public class FilesystemShellTest {
         .move(FsPath.absolute("/db1/table1.csv"), 
FsPath.absolute("/db1/table2.csv"));
   }
 
+  @Test
+  public void executeStandardWriteCommandsWhenEnabled() throws SQLException {
+    shell = new FilesystemShell(shellContext(), provider, mutationProvider, 
true);
+
+    assertTrue(shell.execute("rmdir /db1"));
+    assertTrue(shell.execute("rm -r /db2"));
+    assertTrue(shell.execute("cp /db1/table1.schema /db1/table2.schema"));
+
+    verify(mutationProvider).rmdir(FsPath.absolute("/db1"));
+    verify(mutationProvider).removeRecursive(FsPath.absolute("/db2"));
+    verify(mutationProvider)
+        .copy(FsPath.absolute("/db1/table1.schema"), 
FsPath.absolute("/db1/table2.schema"));
+  }
+
+  @Test
+  public void executeRecursiveRemoveRejectsReadOnlyMode() throws SQLException {
+    assertTrue(shell.execute("rm -r /db1"));
+
+    assertTrue(out.toString().contains("rm: /db1: Read-only file system"));
+    verifyZeroInteractions(mutationProvider);
+  }
+
   @Test
   public void executeTeeRejectsReadOnlyMode() throws SQLException {
     assertTrue(shell.execute("tee -a /db1/table1.csv"));
@@ -321,6 +343,28 @@ public class FilesystemShellTest {
     verify(provider).list(FsPath.absolute("/root"));
   }
 
+  @Test
+  public void executeLsRecursivePrintsChildren() throws SQLException {
+    when(provider.describe(FsPath.absolute("/")))
+        .thenReturn(new FsNode("/", FsPath.absolute("/"), 
FsNodeType.VIRTUAL_ROOT));
+    when(provider.list(FsPath.absolute("/")))
+        .thenReturn(
+            Arrays.asList(new FsNode("db1", FsPath.absolute("/db1"), 
FsNodeType.TABLE_DATABASE)));
+    when(provider.list(FsPath.absolute("/db1")))
+        .thenReturn(
+            Arrays.asList(
+                new FsNode(
+                    "table1.csv", FsPath.absolute("/db1/table1.csv"), 
FsNodeType.TABLE_DATA_FILE)));
+
+    assertTrue(shell.execute("ls -R /"));
+
+    assertTrue(out.toString().contains("db1"));
+    assertTrue(out.toString().contains("table1.csv"));
+    verify(provider).describe(FsPath.absolute("/"));
+    verify(provider).list(FsPath.absolute("/"));
+    verify(provider).list(FsPath.absolute("/db1"));
+  }
+
   @Test
   public void executeTreeUnknownPathPrintsNoSuchFile() throws SQLException {
     when(provider.describe(FsPath.absolute("/db1/table1")))
diff --git 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParserTest.java
 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParserTest.java
index df0bf2ffdc0..edf9f4bb6be 100644
--- 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParserTest.java
+++ 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/command/FilesystemCommandParserTest.java
@@ -292,6 +292,42 @@ public class FilesystemCommandParserTest {
     assertEquals("/db1/table2.csv", mv.getPaths().get(1));
   }
 
+  @Test
+  public void parseRmdirCommand() {
+    FilesystemCommand command = FilesystemCommandParser.parse("rmdir /db1");
+
+    assertEquals(FilesystemCommand.Type.RMDIR, command.getType());
+    assertEquals("/db1", command.getPath());
+  }
+
+  @Test
+  public void parseRmRecursiveCommand() {
+    FilesystemCommand command = FilesystemCommandParser.parse("rm -r /db1");
+
+    assertEquals(FilesystemCommand.Type.RM, command.getType());
+    assertEquals("-r", command.getOption());
+    assertEquals("/db1", command.getPath());
+  }
+
+  @Test
+  public void parseCpCommand() {
+    FilesystemCommand command =
+        FilesystemCommandParser.parse("cp /db1/table1.schema 
/db1/table2.schema");
+
+    assertEquals(FilesystemCommand.Type.CP, command.getType());
+    assertEquals(2, command.getPaths().size());
+    assertEquals("/db1/table1.schema", command.getPaths().get(0));
+    assertEquals("/db1/table2.schema", command.getPaths().get(1));
+  }
+
+  @Test
+  public void parseLsRecursiveAsTreeCommand() {
+    FilesystemCommand command = FilesystemCommandParser.parse("ls -R /db1");
+
+    assertEquals(FilesystemCommand.Type.TREE, command.getType());
+    assertEquals("/db1", command.getPath());
+  }
+
   @Test
   public void parseTreeDepthBeforePath() {
     FilesystemCommand command = FilesystemCommandParser.parse("tree -L 2 
/root/sg");
diff --git 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.java
 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.java
index 17efc72a76a..0a944b223a1 100644
--- 
a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.java
+++ 
b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.java
@@ -77,6 +77,28 @@ public class TableFilesystemMutationProviderTest {
     assertInvalidOperation(() -> 
provider.remove(FsPath.absolute("/db1/table1/s1")));
   }
 
+  @Test
+  public void rmdirDatabaseDropsDatabase() throws SQLException {
+    provider.rmdir(FsPath.absolute("/db1"));
+
+    verify(executor).execute("DROP DATABASE db1");
+  }
+
+  @Test
+  public void removeRecursiveDatabaseDropsDatabase() throws SQLException {
+    provider.removeRecursive(FsPath.absolute("/db1"));
+
+    verify(executor).execute("DROP DATABASE db1");
+  }
+
+  @Test
+  public void rmdirAndRemoveRecursiveRejectUnsafeLevels() throws SQLException {
+    assertInvalidOperation(() -> provider.rmdir(FsPath.absolute("/")));
+    assertInvalidOperation(() -> 
provider.rmdir(FsPath.absolute("/db1/table1.csv")));
+    assertInvalidOperation(() -> 
provider.removeRecursive(FsPath.absolute("/")));
+    assertInvalidOperation(() -> 
provider.removeRecursive(FsPath.absolute("/db1/table1.csv")));
+  }
+
   @Test
   public void moveTableCsvRenamesTableInSameDatabase() throws SQLException {
     provider.move(FsPath.absolute("/db1/table1.csv"), 
FsPath.absolute("/db1/table2.csv"));
@@ -100,6 +122,22 @@ public class TableFilesystemMutationProviderTest {
             provider.move(FsPath.absolute("/db1/table1.csv"), 
FsPath.absolute("/db2/table1.csv")));
   }
 
+  @Test
+  public void copySchemaCreatesTableLikeSource() throws SQLException {
+    provider.copy(FsPath.absolute("/db1/table1.schema"), 
FsPath.absolute("/db1/table2.schema"));
+
+    verify(executor).execute("CREATE TABLE db1.table2 LIKE db1.table1");
+  }
+
+  @Test
+  public void copyRejectsNonSchemaPaths() throws SQLException {
+    assertInvalidOperation(
+        () ->
+            provider.copy(FsPath.absolute("/db1/table1.csv"), 
FsPath.absolute("/db1/table2.csv")));
+    assertInvalidOperation(
+        () -> provider.copy(FsPath.absolute("/db1/table1.schema"), 
FsPath.absolute("/db1")));
+  }
+
   @Test
   public void appendCsvWithHeaderBuildsMultiRowInsert() throws SQLException {
     mockTableSchema();

Reply via email to