add unix and extend
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 0000000..6356677
--- /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 407744f..508a084 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 @@
   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 @@
       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 @@
     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 @@
     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 @@
     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 7d37923..14a6f19 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 @@
     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 565b9ba..8b1aea2 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 @@
     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 @@
       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 @@
     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 @@
             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 @@
         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 3d1d3af..81c7df6 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 @@
 
   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 710ff02..e4642be 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 @@
   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;
 
@@ -46,6 +47,11 @@
   }
 
   @Override
+  public void rmdir(FsPath path) throws SQLException {
+    dropDatabase(path);
+  }
+
+  @Override
   public void remove(FsPath path) throws SQLException {
     if (!isDataFile(path)) {
       throw invalidOperation();
@@ -54,6 +60,11 @@
   }
 
   @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)) {
       throw invalidOperation();
@@ -69,6 +80,18 @@
   }
 
   @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)) {
       throw invalidOperation();
@@ -91,10 +114,21 @@
     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 @@
     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 2d061c3..9b3cf6b 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
@@ -34,16 +34,31 @@
   }
 
   @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 6cb435e..78e9e88 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
@@ -248,6 +248,28 @@
   }
 
   @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"));
 
@@ -322,6 +344,28 @@
   }
 
   @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")))
         .thenReturn(new FsNode("table1", FsPath.absolute("/db1/table1"), FsNodeType.UNKNOWN));
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 df0bf2f..edf9f4b 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
@@ -293,6 +293,42 @@
   }
 
   @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 17efc72..0a944b2 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
@@ -78,6 +78,28 @@
   }
 
   @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"));
 
@@ -101,6 +123,22 @@
   }
 
   @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();