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();
