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 08ad58029e76a817b82a75ef2445a4206381a6d0 Author: spricoder <[email protected]> AuthorDate: Wed Apr 29 20:50:12 2026 +0800 add write --- .../java/org/apache/iotdb/cli/AbstractCli.java | 35 +++++ .../src/main/java/org/apache/iotdb/cli/Cli.java | 15 ++- .../org/apache/iotdb/cli/fs/FilesystemShell.java | 142 ++++++++++++++++++++- .../iotdb/cli/fs/command/FilesystemCommand.java | 57 ++++++++- .../cli/fs/command/FilesystemCommandParser.java | 127 ++++++++++++++++++ .../FilesystemMutationProvider.java} | 13 +- .../cli/fs/provider/FilesystemSchemaProvider.java | 8 ++ .../provider/TableFilesystemMutationProvider.java | 86 +++++++++++++ .../fs/provider/TableFilesystemSchemaProvider.java | 48 +++++++ .../fs/provider/TreeFilesystemSchemaProvider.java | 29 +++++ ... => UnsupportedFilesystemMutationProvider.java} | 29 +++-- .../apache/iotdb/cli/fs/sql/JdbcSqlExecutor.java | 7 + .../org/apache/iotdb/cli/fs/sql/SqlExecutor.java | 2 + .../org/apache/iotdb/cli/utils/JlineUtils.java | 3 +- .../java/org/apache/iotdb/cli/AbstractCliTest.java | 29 +++++ .../apache/iotdb/cli/CliFilesystemModeTest.java | 26 ++++ .../apache/iotdb/cli/fs/FilesystemShellTest.java | 132 ++++++++++++++++++- .../fs/command/FilesystemCommandParserTest.java | 65 ++++++++++ .../TableFilesystemMutationProviderTest.java | 103 +++++++++++++++ .../TableFilesystemSchemaProviderTest.java | 26 ++++ .../iotdb/cli/fs/sql/JdbcSqlExecutorTest.java | 10 ++ 21 files changed, 963 insertions(+), 29 deletions(-) diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/AbstractCli.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/AbstractCli.java index 5e7680d2cc5..1fce4ae8e27 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/AbstractCli.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/AbstractCli.java @@ -74,6 +74,10 @@ public abstract class AbstractCli { static final String ACCESS_MODE_NAME = "access mode"; static final String ACCESS_MODE_SQL = "sql"; static final String ACCESS_MODE_FILESYSTEM = "filesystem"; + static final String FS_WRITE_MODE_ARGS = "fs_write_mode"; + static final String FS_WRITE_MODE_NAME = "fs write mode"; + static final String FS_WRITE_MODE_DISABLED = "disabled"; + static final String FS_WRITE_MODE_ENABLED = "enabled"; private static final String EXECUTE_ARGS = "e"; @@ -141,6 +145,7 @@ public abstract class AbstractCli { static String execute; static boolean hasExecuteSQL = false; static String accessMode = ACCESS_MODE_SQL; + static String fsWriteMode = FS_WRITE_MODE_DISABLED; static Set<String> keywordSet = new HashSet<>(); @@ -166,6 +171,7 @@ public abstract class AbstractCli { keywordSet.add("-" + ISO8601_ARGS); keywordSet.add("-" + RPC_COMPRESS_ARGS); keywordSet.add("--" + ACCESS_MODE_ARGS); + keywordSet.add("--" + FS_WRITE_MODE_ARGS); } static Options createOptions() { @@ -238,6 +244,17 @@ public abstract class AbstractCli { .build(); options.addOption(accessMode); + Option fsWriteMode = + Option.builder() + .longOpt(FS_WRITE_MODE_ARGS) + .argName(FS_WRITE_MODE_NAME) + .hasArg() + .desc( + "Filesystem write mode, supports disabled and enabled. Default is disabled." + + " (optional)") + .build(); + options.addOption(fsWriteMode); + Option isRpcCompressed = Option.builder(RPC_COMPRESS_ARGS) .argName(RPC_COMPRESS_NAME) @@ -281,6 +298,24 @@ public abstract class AbstractCli { AbstractCli.accessMode = accessMode; } + static String getFsWriteMode(CliContext ctx, CommandLine commandLine) throws ArgsErrorException { + String value = commandLine.getOptionValue(FS_WRITE_MODE_ARGS, FS_WRITE_MODE_DISABLED); + String normalized = value.toLowerCase(Locale.ROOT); + if (FS_WRITE_MODE_DISABLED.equals(normalized) || FS_WRITE_MODE_ENABLED.equals(normalized)) { + return normalized; + } + String msg = + String.format( + "%s: Unsupported fs write mode '%s'. Supported values are disabled and enabled.", + IOTDB, value); + ctx.getPrinter().println(msg); + throw new ArgsErrorException(msg); + } + + static void setFsWriteMode(String fsWriteMode) { + AbstractCli.fsWriteMode = fsWriteMode; + } + static String checkRequiredArg( CliContext ctx, String arg, diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java index 4ded47379b9..ad5571efb41 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/Cli.java @@ -20,9 +20,12 @@ package org.apache.iotdb.cli; import org.apache.iotdb.cli.fs.FilesystemShell; +import org.apache.iotdb.cli.fs.provider.FilesystemMutationProvider; import org.apache.iotdb.cli.fs.provider.FilesystemSchemaProvider; +import org.apache.iotdb.cli.fs.provider.TableFilesystemMutationProvider; import org.apache.iotdb.cli.fs.provider.TableFilesystemSchemaProvider; import org.apache.iotdb.cli.fs.provider.TreeFilesystemSchemaProvider; +import org.apache.iotdb.cli.fs.provider.UnsupportedFilesystemMutationProvider; import org.apache.iotdb.cli.fs.sql.JdbcSqlExecutor; import org.apache.iotdb.cli.type.ExitType; import org.apache.iotdb.cli.utils.CliContext; @@ -145,6 +148,7 @@ public class Cli extends AbstractCli { setSqlDialect(commandLine.getOptionValue(Config.SQL_DIALECT)); } setAccessMode(getAccessMode(ctx, commandLine)); + setFsWriteMode(getFsWriteMode(ctx, commandLine)); } catch (ArgsErrorException e) { ctx.getPrinter() .println(IOTDB_ERROR_PREFIX + ": Input params error because " + e.getMessage()); @@ -224,6 +228,11 @@ public class Cli extends AbstractCli { .println( IOTDB_ERROR_PREFIX + ": Can't execute filesystem command because " + e.getMessage()); ctx.exit(CODE_ERROR); + } catch (TException e) { + ctx.getPrinter() + .println( + IOTDB_ERROR_PREFIX + ": Can't execute filesystem command because " + e.getMessage()); + ctx.exit(CODE_ERROR); } } @@ -264,12 +273,16 @@ public class Cli extends AbstractCli { static FilesystemShell createFilesystemShell(CliContext ctx, IoTDBConnection connection) { JdbcSqlExecutor executor = new JdbcSqlExecutor(connection); FilesystemSchemaProvider provider; + FilesystemMutationProvider mutationProvider; if (Constant.TABLE_DIALECT.equalsIgnoreCase(connection.getSqlDialect())) { provider = new TableFilesystemSchemaProvider(executor); + mutationProvider = new TableFilesystemMutationProvider(executor); } else { provider = new TreeFilesystemSchemaProvider(executor); + mutationProvider = new UnsupportedFilesystemMutationProvider(); } - return new FilesystemShell(ctx, provider); + return new FilesystemShell( + ctx, provider, mutationProvider, FS_WRITE_MODE_ENABLED.equals(fsWriteMode)); } private static boolean readerReadLine(CliContext ctx, IoTDBConnection connection) { 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 6a3cf9e976d..8929c964524 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 @@ -24,7 +24,9 @@ import org.apache.iotdb.cli.fs.command.FilesystemCommandParser; import org.apache.iotdb.cli.fs.node.FsNode; import org.apache.iotdb.cli.fs.node.FsNodeType; import org.apache.iotdb.cli.fs.path.FsPath; +import org.apache.iotdb.cli.fs.provider.FilesystemMutationProvider; import org.apache.iotdb.cli.fs.provider.FilesystemSchemaProvider; +import org.apache.iotdb.cli.fs.provider.UnsupportedFilesystemMutationProvider; import org.apache.iotdb.cli.fs.sql.SqlRow; import org.apache.iotdb.cli.utils.CliContext; @@ -44,15 +46,28 @@ public class FilesystemShell { private static final int DEFAULT_READ_LIMIT = 20; private static final List<String> COMMANDS = Arrays.asList( - "pwd", "ls", "ll", "cd", "stat", "cat", "head", "paste", "tree", "help", "exit", "quit"); + "pwd", "ls", "ll", "cd", "stat", "cat", "head", "tail", "wc", "grep", "find", "less", + "more", "file", "du", "mkdir", "rm", "mv", "paste", "tree", "help", "exit", "quit"); private final CliContext ctx; private final FilesystemSchemaProvider provider; + private final FilesystemMutationProvider mutationProvider; + private final boolean writeEnabled; private FsPath currentPath = FsPath.absolute("/"); public FilesystemShell(CliContext ctx, FilesystemSchemaProvider provider) { + this(ctx, provider, new UnsupportedFilesystemMutationProvider(), false); + } + + public FilesystemShell( + CliContext ctx, + FilesystemSchemaProvider provider, + FilesystemMutationProvider mutationProvider, + boolean writeEnabled) { this.ctx = ctx; this.provider = provider; + this.mutationProvider = mutationProvider; + this.writeEnabled = writeEnabled; } public boolean execute(String input) throws SQLException { @@ -79,6 +94,37 @@ public class FilesystemShell { case HEAD: printRows(provider.read(resolve(command.getPath()), command.getLimit())); return true; + case TAIL: + printRows(provider.tail(resolve(command.getPath()), command.getLimit())); + return true; + case WC: + printLineCount(command.getPath()); + return true; + case GREP: + printMatchingRows(command.getPath(), command.getPattern()); + return true; + case FIND: + printFind(resolve(command.getPath()), command.getPattern()); + return true; + case LESS: + case MORE: + printRows(provider.read(resolve(command.getPath()), DEFAULT_READ_LIMIT)); + return true; + case FILE: + printFile(command.getPath()); + return true; + case DU: + printDiskUsage(command.getPath()); + return true; + case MKDIR: + mkdir(command.getPath()); + return true; + case RM: + remove(command.getPath()); + return true; + case MV: + move(command.getPaths()); + return true; case PASTE: printRows(provider.read(resolve(command.getPaths()), DEFAULT_READ_LIMIT)); return true; @@ -151,8 +197,15 @@ public class FilesystemShell { } private void printNodes(List<FsNode> nodes) { + StringBuilder builder = new StringBuilder(); for (FsNode node : nodes) { - ctx.getPrinter().println(node.getName()); + if (builder.length() > 0) { + builder.append(','); + } + builder.append(node.getName()); + } + if (builder.length() > 0) { + ctx.getPrinter().println(builder.toString()); } } @@ -181,6 +234,80 @@ public class FilesystemShell { } } + private void printLineCount(String path) throws SQLException { + FsPath resolvedPath = resolve(path); + ctx.getPrinter().println(provider.count(resolvedPath) + " " + resolvedPath); + } + + private void printMatchingRows(String path, String pattern) throws SQLException { + for (SqlRow row : provider.read(resolve(path), DEFAULT_READ_LIMIT)) { + String line = joinValues(row); + if (line.contains(pattern)) { + ctx.getPrinter().println(line); + } + } + } + + private void printFind(FsPath path, String pattern) throws SQLException { + FsNode node = provider.describe(path); + if (matchesFind(node, pattern)) { + ctx.getPrinter().println(path.toString()); + } + if (!isDirectory(node.getType())) { + return; + } + for (FsNode child : provider.list(path)) { + printFind(child.getPath(), pattern); + } + } + + private static boolean matchesFind(FsNode node, String pattern) { + return pattern == null || pattern.isEmpty() || node.getName().equals(pattern); + } + + private void printFile(String path) throws SQLException { + FsPath resolvedPath = resolve(path); + ctx.getPrinter().println(resolvedPath + ": " + provider.describe(resolvedPath).getType()); + } + + private void printDiskUsage(String path) throws SQLException { + FsPath resolvedPath = resolve(path); + ctx.getPrinter().println(provider.count(resolvedPath) + "\t" + resolvedPath); + } + + private void mkdir(String path) throws SQLException { + FsPath resolvedPath = resolve(path); + if (!ensureWritable("mkdir", resolvedPath)) { + return; + } + mutationProvider.mkdir(resolvedPath); + } + + private void remove(String path) throws SQLException { + FsPath resolvedPath = resolve(path); + if (!ensureWritable("rm", resolvedPath)) { + return; + } + mutationProvider.remove(resolvedPath); + } + + private void move(List<String> paths) throws SQLException { + FsPath source = resolve(paths.get(0)); + FsPath target = resolve(paths.get(1)); + if (!ensureWritable("mv", source)) { + return; + } + mutationProvider.move(source, target); + } + + private boolean ensureWritable(String command, FsPath path) { + if (writeEnabled) { + return true; + } + ctx.getPrinter().println(command + ": " + path + ": Read-only file system"); + return false; + } + private static String joinValues(SqlRow row) { StringBuilder builder = new StringBuilder(); for (String value : row.asMap().values()) { @@ -202,6 +329,17 @@ public class FilesystemShell { ctx.getPrinter().println("stat [path]"); ctx.getPrinter().println("cat <path>..."); ctx.getPrinter().println("head [-n lines] <path>"); + ctx.getPrinter().println("tail [-n lines] <path>"); + ctx.getPrinter().println("wc -l <path>"); + ctx.getPrinter().println("grep <pattern> <path>"); + ctx.getPrinter().println("find [path] [-name pattern]"); + ctx.getPrinter().println("less <path>"); + ctx.getPrinter().println("more <path>"); + ctx.getPrinter().println("file <path>"); + ctx.getPrinter().println("du <path>"); + ctx.getPrinter().println("mkdir <path>"); + ctx.getPrinter().println("rm <path>"); + ctx.getPrinter().println("mv <source> <target>"); ctx.getPrinter().println("paste <path>..."); ctx.getPrinter().println("tree [-L depth] [path]"); ctx.getPrinter().println("exit"); 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 2cdd492fdc5..67aa6561d41 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 @@ -32,6 +32,17 @@ public class FilesystemCommand { STAT, CAT, HEAD, + TAIL, + WC, + GREP, + FIND, + LESS, + MORE, + FILE, + DU, + MKDIR, + RM, + MV, PASTE, TREE, SQL, @@ -45,6 +56,8 @@ public class FilesystemCommand { private final List<String> paths; private final int depth; private final int limit; + private final String option; + private final String pattern; private final String statement; private final String errorMessage; @@ -54,6 +67,8 @@ public class FilesystemCommand { List<String> paths, int depth, int limit, + String option, + String pattern, String statement, String errorMessage) { this.type = type; @@ -61,40 +76,60 @@ public class FilesystemCommand { this.paths = paths; this.depth = depth; this.limit = limit; + this.option = option; + this.pattern = pattern; this.statement = statement; this.errorMessage = errorMessage; } public static FilesystemCommand simple(Type type) { - return new FilesystemCommand(type, "", Collections.emptyList(), -1, -1, "", ""); + return new FilesystemCommand(type, "", Collections.emptyList(), -1, -1, "", "", "", ""); } public static FilesystemCommand path(Type type, String path) { - return new FilesystemCommand(type, path, Collections.singletonList(path), -1, -1, "", ""); + return new FilesystemCommand( + type, path, Collections.singletonList(path), -1, -1, "", "", "", ""); } public static FilesystemCommand paths(Type type, List<String> paths) { String path = paths.isEmpty() ? "" : paths.get(0); - return new FilesystemCommand(type, path, Collections.unmodifiableList(paths), -1, -1, "", ""); + return new FilesystemCommand( + type, path, Collections.unmodifiableList(paths), -1, -1, "", "", "", ""); } public static FilesystemCommand head(String path, int limit) { return new FilesystemCommand( - Type.HEAD, path, Collections.singletonList(path), -1, limit, "", ""); + Type.HEAD, path, Collections.singletonList(path), -1, limit, "", "", "", ""); + } + + public static FilesystemCommand tail(String path, int limit) { + return new FilesystemCommand( + Type.TAIL, path, Collections.singletonList(path), -1, limit, "", "", "", ""); + } + + public static FilesystemCommand option(Type type, String option, String path) { + return new FilesystemCommand( + type, path, Collections.singletonList(path), -1, -1, option, "", "", ""); + } + + public static FilesystemCommand pattern(Type type, String pattern, String path) { + return new FilesystemCommand( + type, path, Collections.singletonList(path), -1, -1, "", pattern, "", ""); } public static FilesystemCommand tree(String path, int depth) { return new FilesystemCommand( - Type.TREE, path, Collections.singletonList(path), depth, -1, "", ""); + Type.TREE, path, Collections.singletonList(path), depth, -1, "", "", "", ""); } public static FilesystemCommand sql(String statement) { - return new FilesystemCommand(Type.SQL, "", Collections.emptyList(), -1, -1, statement, ""); + return new FilesystemCommand( + Type.SQL, "", Collections.emptyList(), -1, -1, "", "", statement, ""); } public static FilesystemCommand invalid(String errorMessage) { return new FilesystemCommand( - Type.INVALID, "", Collections.emptyList(), -1, -1, "", errorMessage); + Type.INVALID, "", Collections.emptyList(), -1, -1, "", "", "", errorMessage); } public Type getType() { @@ -117,6 +152,14 @@ public class FilesystemCommand { return limit; } + public String getOption() { + return option; + } + + public String getPattern() { + return pattern; + } + public String getStatement() { return statement; } 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 7bdab81d178..61000c17ee3 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 @@ -71,6 +71,39 @@ public class FilesystemCommandParser { if ("head".equals(command)) { return parseHead(tokens); } + if ("tail".equals(command)) { + return parseTail(tokens); + } + if ("wc".equals(command)) { + return parseWc(tokens); + } + if ("grep".equals(command)) { + return parseGrep(tokens); + } + if ("find".equals(command)) { + return parseFind(tokens); + } + if ("less".equals(command)) { + return FilesystemCommand.path(FilesystemCommand.Type.LESS, pathArgument(tokens)); + } + if ("more".equals(command)) { + return FilesystemCommand.path(FilesystemCommand.Type.MORE, pathArgument(tokens)); + } + if ("file".equals(command)) { + return FilesystemCommand.path(FilesystemCommand.Type.FILE, pathArgument(tokens)); + } + if ("du".equals(command)) { + return FilesystemCommand.path(FilesystemCommand.Type.DU, pathArgument(tokens)); + } + if ("mkdir".equals(command)) { + return FilesystemCommand.path(FilesystemCommand.Type.MKDIR, pathArgument(tokens)); + } + if ("rm".equals(command)) { + return parseRm(tokens); + } + if ("mv".equals(command)) { + return parseMv(tokens); + } if ("paste".equals(command)) { return parsePaste(tokens); } @@ -99,6 +132,26 @@ public class FilesystemCommandParser { return FilesystemCommand.paths(FilesystemCommand.Type.PASTE, paths); } + private static FilesystemCommand parseRm(String[] tokens) { + if (tokens.length < 2) { + return FilesystemCommand.invalid("Missing rm path"); + } + if (tokens[1].startsWith("-")) { + return FilesystemCommand.invalid("Unsupported rm option: " + tokens[1]); + } + return FilesystemCommand.path(FilesystemCommand.Type.RM, tokens[1]); + } + + private static FilesystemCommand parseMv(String[] tokens) { + if (tokens.length < 3) { + return FilesystemCommand.invalid("Usage: mv <source> <target>"); + } + List<String> paths = new ArrayList<>(); + paths.add(tokens[1]); + paths.add(tokens[2]); + return FilesystemCommand.paths(FilesystemCommand.Type.MV, paths); + } + private static FilesystemCommand parseLs(String[] tokens) { FilesystemCommand.Type type = FilesystemCommand.Type.LS; String path = DEFAULT_PATH; @@ -164,6 +217,80 @@ public class FilesystemCommandParser { return FilesystemCommand.head(path, limit); } + private static FilesystemCommand parseTail(String[] tokens) { + String path = DEFAULT_PATH; + int limit = DEFAULT_HEAD_LIMIT; + + for (int i = 1; i < tokens.length; i++) { + String token = tokens[i]; + if ("-n".equals(token)) { + if (i + 1 >= tokens.length) { + return FilesystemCommand.invalid("Missing tail line count"); + } + try { + limit = Integer.parseInt(tokens[++i]); + } catch (NumberFormatException e) { + return FilesystemCommand.invalid("Invalid tail line count: " + tokens[i]); + } + } else if (token.startsWith("-") && token.length() > 1) { + try { + limit = Integer.parseInt(token.substring(1)); + } catch (NumberFormatException e) { + return FilesystemCommand.invalid("Unsupported tail option: " + token); + } + } else { + path = token; + } + } + + if (limit < 0) { + return FilesystemCommand.invalid("Invalid tail line count: " + limit); + } + return FilesystemCommand.tail(path, limit); + } + + private static FilesystemCommand parseWc(String[] tokens) { + String option = "-l"; + String path = DEFAULT_PATH; + + for (int i = 1; i < tokens.length; i++) { + String token = tokens[i]; + if (token.startsWith("-")) { + if (!"-l".equals(token)) { + return FilesystemCommand.invalid("Unsupported wc option: " + token); + } + option = token; + } else { + path = token; + } + } + return FilesystemCommand.option(FilesystemCommand.Type.WC, option, path); + } + + private static FilesystemCommand parseGrep(String[] tokens) { + if (tokens.length < 3) { + return FilesystemCommand.invalid("Usage: grep <pattern> <path>"); + } + return FilesystemCommand.pattern(FilesystemCommand.Type.GREP, tokens[1], tokens[2]); + } + + private static FilesystemCommand parseFind(String[] tokens) { + String path = tokens.length > 1 ? tokens[1] : DEFAULT_PATH; + String pattern = ""; + + for (int i = 2; i < tokens.length; i++) { + if ("-name".equals(tokens[i])) { + if (i + 1 >= tokens.length) { + return FilesystemCommand.invalid("Missing find name pattern"); + } + pattern = tokens[++i]; + } else { + return FilesystemCommand.invalid("Unsupported find option: " + tokens[i]); + } + } + return FilesystemCommand.pattern(FilesystemCommand.Type.FIND, pattern, path); + } + private static FilesystemCommand parseTree(String[] tokens) { String path = DEFAULT_PATH; int depth = DEFAULT_TREE_DEPTH; diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java similarity index 74% copy from iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java copy to iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java index c372c2e3509..325025178ed 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemMutationProvider.java @@ -17,12 +17,17 @@ * under the License. */ -package org.apache.iotdb.cli.fs.sql; +package org.apache.iotdb.cli.fs.provider; + +import org.apache.iotdb.cli.fs.path.FsPath; import java.sql.SQLException; -import java.util.List; -public interface SqlExecutor { +public interface FilesystemMutationProvider { + + void mkdir(FsPath path) throws SQLException; + + void remove(FsPath path) throws SQLException; - List<SqlRow> query(String sql) throws SQLException; + void move(FsPath source, FsPath target) throws SQLException; } diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java index f96f617f363..de92c9bf983 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java @@ -34,6 +34,14 @@ public interface FilesystemSchemaProvider { List<SqlRow> read(FsPath path, int limit) throws SQLException; + default List<SqlRow> tail(FsPath path, int limit) throws SQLException { + throw new SQLException("Path does not support tail: " + path); + } + + default long count(FsPath path) throws SQLException { + throw new SQLException("Path does not support count: " + path); + } + default List<SqlRow> read(List<FsPath> paths, int limit) throws SQLException { if (paths.size() == 1) { return read(paths.get(0), limit); 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 new file mode 100644 index 00000000000..153bd53d9ae --- /dev/null +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProvider.java @@ -0,0 +1,86 @@ +/* + * 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.iotdb.cli.fs.provider; + +import org.apache.iotdb.cli.fs.path.FsPath; +import org.apache.iotdb.cli.fs.sql.SqlExecutor; + +import java.sql.SQLException; +import java.util.List; + +public class TableFilesystemMutationProvider implements FilesystemMutationProvider { + + private static final String INVALID_WRITE_OPERATION = + "Invalid filesystem write operation for this path"; + + private final SqlExecutor executor; + + public TableFilesystemMutationProvider(SqlExecutor executor) { + this.executor = executor; + } + + @Override + public void mkdir(FsPath path) throws SQLException { + if (path.getSegments().size() != 1) { + throw invalidOperation(); + } + executor.execute("CREATE DATABASE " + path.getFileName()); + } + + @Override + public void remove(FsPath path) throws SQLException { + if (path.getSegments().size() != 2) { + throw invalidOperation(); + } + executor.execute("DROP TABLE " + toTablePath(path)); + } + + @Override + public void move(FsPath source, FsPath target) throws SQLException { + if (source.getSegments().size() != 2 || target.getSegments().size() != 2) { + throw invalidOperation(); + } + if (!parent(source).equals(parent(target))) { + throw invalidOperation(); + } + executor.execute("ALTER TABLE " + toTablePath(source) + " RENAME TO " + target.getFileName()); + } + + private static SQLException invalidOperation() { + return new SQLException(INVALID_WRITE_OPERATION); + } + + private static String toTablePath(FsPath path) { + List<String> segments = path.getSegments(); + return segments.get(0) + "." + segments.get(1); + } + + private static FsPath parent(FsPath path) { + List<String> segments = path.getSegments(); + StringBuilder builder = new StringBuilder("/"); + for (int i = 0; i < segments.size() - 1; i++) { + if (i > 0) { + builder.append('/'); + } + builder.append(segments.get(i)); + } + return FsPath.absolute(builder.toString()); + } +} diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProvider.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProvider.java index 031ca186ab8..d735fa99a8b 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProvider.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProvider.java @@ -27,6 +27,7 @@ import org.apache.iotdb.cli.fs.sql.SqlRow; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class TableFilesystemSchemaProvider implements FilesystemSchemaProvider { @@ -87,6 +88,46 @@ public class TableFilesystemSchemaProvider implements FilesystemSchemaProvider { throw new SQLException("Path is not readable: " + path); } + @Override + public List<SqlRow> tail(FsPath path, int limit) throws SQLException { + int depth = path.getSegments().size(); + List<SqlRow> rows; + if (depth == 2) { + rows = + executor.query( + "SELECT * FROM " + toTablePath(path) + " ORDER BY time DESC LIMIT " + limit); + } else if (depth == 3) { + String tablePath = toTablePath(parent(path)); + rows = + executor.query( + "SELECT " + + path.getFileName() + + " FROM " + + tablePath + + " ORDER BY time DESC LIMIT " + + limit); + } else { + throw new SQLException("Path is not readable: " + path); + } + Collections.reverse(rows); + return rows; + } + + @Override + public long count(FsPath path) throws SQLException { + int depth = path.getSegments().size(); + List<SqlRow> rows; + if (depth == 2) { + rows = executor.query("SELECT COUNT(*) FROM " + toTablePath(path)); + } else if (depth == 3) { + String tablePath = toTablePath(parent(path)); + rows = executor.query("SELECT COUNT(" + path.getFileName() + ") FROM " + tablePath); + } else { + throw new SQLException("Path is not countable: " + path); + } + return countValue(rows); + } + @Override public List<SqlRow> read(List<FsPath> paths, int limit) throws SQLException { if (paths.size() == 1) { @@ -182,6 +223,13 @@ public class TableFilesystemSchemaProvider implements FilesystemSchemaProvider { return builder.toString(); } + private static long countValue(List<SqlRow> rows) { + if (rows.isEmpty() || rows.get(0).asMap().isEmpty()) { + return 0; + } + return Long.parseLong(rows.get(0).asMap().values().iterator().next()); + } + 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/TreeFilesystemSchemaProvider.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TreeFilesystemSchemaProvider.java index 0766bb271d4..292cb5da540 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TreeFilesystemSchemaProvider.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/TreeFilesystemSchemaProvider.java @@ -27,6 +27,7 @@ import org.apache.iotdb.cli.fs.sql.SqlRow; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -78,6 +79,34 @@ public class TreeFilesystemSchemaProvider implements FilesystemSchemaProvider { "SELECT " + measurement + " FROM " + toTreePath(devicePath) + " LIMIT " + limit); } + @Override + public List<SqlRow> tail(FsPath path, int limit) throws SQLException { + String measurement = path.getFileName(); + FsPath devicePath = parent(path); + List<SqlRow> rows = + executor.query( + "SELECT " + + measurement + + " FROM " + + toTreePath(devicePath) + + " ORDER BY time DESC LIMIT " + + limit); + Collections.reverse(rows); + return rows; + } + + @Override + public long count(FsPath path) throws SQLException { + String measurement = path.getFileName(); + FsPath devicePath = parent(path); + List<SqlRow> rows = + executor.query("SELECT COUNT(" + measurement + ") FROM " + toTreePath(devicePath)); + if (rows.isEmpty() || rows.get(0).asMap().isEmpty()) { + return 0; + } + return Long.parseLong(rows.get(0).asMap().values().iterator().next()); + } + private List<FsNode> listTreeRoots() throws SQLException { Set<String> roots = new LinkedHashSet<>(); for (SqlRow row : executor.query("SHOW DATABASES")) { diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java similarity index 62% copy from iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java copy to iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java index f96f617f363..9f4a08ceb01 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/FilesystemSchemaProvider.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/provider/UnsupportedFilesystemMutationProvider.java @@ -19,25 +19,30 @@ package org.apache.iotdb.cli.fs.provider; -import org.apache.iotdb.cli.fs.node.FsNode; import org.apache.iotdb.cli.fs.path.FsPath; -import org.apache.iotdb.cli.fs.sql.SqlRow; import java.sql.SQLException; -import java.util.List; -public interface FilesystemSchemaProvider { +public class UnsupportedFilesystemMutationProvider implements FilesystemMutationProvider { - List<FsNode> list(FsPath path) throws SQLException; + private static final String UNSUPPORTED = "Filesystem write operation is not supported"; - FsNode describe(FsPath path) throws SQLException; + @Override + public void mkdir(FsPath path) throws SQLException { + throw unsupported(); + } + + @Override + public void remove(FsPath path) throws SQLException { + throw unsupported(); + } - List<SqlRow> read(FsPath path, int limit) throws SQLException; + @Override + public void move(FsPath source, FsPath target) throws SQLException { + throw unsupported(); + } - default List<SqlRow> read(List<FsPath> paths, int limit) throws SQLException { - if (paths.size() == 1) { - return read(paths.get(0), limit); - } - throw new SQLException("Multiple paths are not readable by this provider"); + private static SQLException unsupported() { + return new SQLException(UNSUPPORTED); } } diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutor.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutor.java index 31a5925a64b..36a7e9016ee 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutor.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutor.java @@ -54,4 +54,11 @@ public class JdbcSqlExecutor implements SqlExecutor { return rows; } } + + @Override + public void execute(String sql) throws SQLException { + try (Statement statement = connection.createStatement()) { + statement.execute(sql); + } + } } diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java index c372c2e3509..6a6cb7dd39a 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/fs/sql/SqlExecutor.java @@ -25,4 +25,6 @@ import java.util.List; public interface SqlExecutor { List<SqlRow> query(String sql) throws SQLException; + + void execute(String sql) throws SQLException; } diff --git a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java index 0a98529f6af..aa68d220e77 100644 --- a/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java +++ b/iotdb-client/cli/src/main/java/org/apache/iotdb/cli/utils/JlineUtils.java @@ -152,7 +152,8 @@ public class JlineUtils { static Completer createCompleter(String accessMode) { if ("filesystem".equalsIgnoreCase(accessMode)) { return new StringsCompleter( - "pwd", "ls", "ll", "cd", "stat", "cat", "head", "paste", "tree", "help", "exit", "quit"); + "pwd", "ls", "ll", "cd", "stat", "cat", "head", "tail", "wc", "grep", "find", "less", + "more", "file", "du", "mkdir", "rm", "mv", "paste", "tree", "help", "exit", "quit"); } return new StringsCompleter(SQL_KEYWORDS); } diff --git a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/AbstractCliTest.java b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/AbstractCliTest.java index aa6ed5332fd..f2de555a8aa 100644 --- a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/AbstractCliTest.java +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/AbstractCliTest.java @@ -136,6 +136,35 @@ public class AbstractCliTest { assertEquals(AbstractCli.ACCESS_MODE_FILESYSTEM, AbstractCli.getAccessMode(ctx, commandLine)); } + @Test + public void testFsWriteModeEnabled() throws ParseException, ArgsErrorException { + CliContext ctx = new CliContext(System.in, System.out, System.err, ExitType.EXCEPTION); + Options options = AbstractCli.createOptions(); + CommandLineParser parser = new DefaultParser(); + CommandLine commandLine = + parser.parse(options, new String[] {"-u", "root", "--fs_write_mode", "enabled"}); + + assertEquals(AbstractCli.FS_WRITE_MODE_ENABLED, AbstractCli.getFsWriteMode(ctx, commandLine)); + } + + @Test + public void testFsWriteModeRejectsInvalidValue() throws ParseException { + CliContext ctx = new CliContext(System.in, System.out, System.err, ExitType.EXCEPTION); + Options options = AbstractCli.createOptions(); + CommandLineParser parser = new DefaultParser(); + CommandLine commandLine = + parser.parse(options, new String[] {"-u", "root", "--fs_write_mode", "bad"}); + + try { + AbstractCli.getFsWriteMode(ctx, commandLine); + fail(); + } catch (ArgsErrorException e) { + assertEquals( + "IoTDB: Unsupported fs write mode 'bad'. Supported values are disabled and enabled.", + e.getMessage()); + } + } + @Test public void testAccessModeRejectsInvalidValue() throws ParseException { CliContext ctx = new CliContext(System.in, System.out, System.err, ExitType.EXCEPTION); diff --git a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliFilesystemModeTest.java b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliFilesystemModeTest.java index e50a51efd68..d3d44a00972 100644 --- a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliFilesystemModeTest.java +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/CliFilesystemModeTest.java @@ -71,6 +71,32 @@ public class CliFilesystemModeTest { verify(statement).executeQuery("SHOW TABLES FROM db1"); } + @Test + public void createFilesystemShellUsesWriteModeForTableDialect() throws Exception { + when(connection.getSqlDialect()).thenReturn("table"); + when(connection.createStatement()).thenReturn(statement); + AbstractCli.setFsWriteMode(AbstractCli.FS_WRITE_MODE_ENABLED); + try { + FilesystemShell shell = Cli.createFilesystemShell(ctx, connection); + shell.execute("mkdir /db1"); + } finally { + AbstractCli.setFsWriteMode(AbstractCli.FS_WRITE_MODE_DISABLED); + } + + verify(statement).execute("CREATE DATABASE db1"); + } + + @Test + public void createFilesystemShellRejectsWriteWhenWriteModeDisabled() throws Exception { + when(connection.getSqlDialect()).thenReturn("table"); + AbstractCli.setFsWriteMode(AbstractCli.FS_WRITE_MODE_DISABLED); + + FilesystemShell shell = Cli.createFilesystemShell(ctx, connection); + shell.execute("mkdir /db1"); + + org.junit.Assert.assertTrue(out.toString().contains("Read-only file system")); + } + @Test public void createFilesystemShellUsesTreeProviderForTreeDialect() throws Exception { when(connection.getSqlDialect()).thenReturn("tree"); 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 e4ea9161a85..7461aad1367 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 @@ -22,6 +22,7 @@ package org.apache.iotdb.cli.fs; import org.apache.iotdb.cli.fs.node.FsNode; import org.apache.iotdb.cli.fs.node.FsNodeType; import org.apache.iotdb.cli.fs.path.FsPath; +import org.apache.iotdb.cli.fs.provider.FilesystemMutationProvider; import org.apache.iotdb.cli.fs.provider.FilesystemSchemaProvider; import org.apache.iotdb.cli.fs.sql.SqlRow; import org.apache.iotdb.cli.type.ExitType; @@ -47,12 +48,15 @@ import java.util.stream.Collectors; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class FilesystemShellTest { @Mock private FilesystemSchemaProvider provider; + @Mock private FilesystemMutationProvider mutationProvider; private ByteArrayOutputStream out; private FilesystemShell shell; @@ -81,11 +85,13 @@ public class FilesystemShellTest { public void executeLsPrintsChildNodes() throws SQLException { when(provider.list(FsPath.absolute("/"))) .thenReturn( - Arrays.asList(new FsNode("root", FsPath.absolute("/root"), FsNodeType.TREE_ROOT))); + Arrays.asList( + new FsNode("root", FsPath.absolute("/root"), FsNodeType.TREE_ROOT), + new FsNode("test", FsPath.absolute("/test"), FsNodeType.TREE_ROOT))); assertTrue(shell.execute("ls /")); - assertTrue(out.toString().contains("root")); + assertTrue(out.toString().contains("root,test")); assertFalse(out.toString().contains("TREE_ROOT")); verify(provider).list(FsPath.absolute("/")); } @@ -136,6 +142,27 @@ public class FilesystemShellTest { assertFalse(shell.execute("exit")); } + @Test + public void executeWriteCommandRejectsReadOnlyMode() throws SQLException { + assertTrue(shell.execute("mkdir /db1")); + + assertTrue(out.toString().contains("mkdir: /db1: Read-only file system")); + verifyZeroInteractions(mutationProvider); + } + + @Test + public void executeWriteCommandsWhenEnabled() throws SQLException { + shell = new FilesystemShell(shellContext(), provider, mutationProvider, true); + + assertTrue(shell.execute("mkdir /db1")); + assertTrue(shell.execute("rm /db1/table1")); + assertTrue(shell.execute("mv /db1/table1 /db1/table2")); + + verify(mutationProvider).mkdir(FsPath.absolute("/db1")); + verify(mutationProvider).remove(FsPath.absolute("/db1/table1")); + verify(mutationProvider).move(FsPath.absolute("/db1/table1"), FsPath.absolute("/db1/table2")); + } + @Test public void executeTreePrintsChildrenUntilDepth() throws SQLException { when(provider.list(FsPath.absolute("/"))) @@ -193,6 +220,99 @@ public class FilesystemShellTest { verify(provider).read(FsPath.absolute("/db1/table1"), 5); } + @Test + public void executeTailReadsPathWithLimit() throws SQLException { + when(provider.tail(FsPath.absolute("/db1/table1"), 3)) + .thenReturn(Arrays.asList(SqlRow.of("Time", "2", "tag1", "b", "s1", "43"))); + + assertTrue(shell.execute("tail -n 3 /db1/table1")); + + assertTrue(out.toString().contains("2\tb\t43")); + verify(provider).tail(FsPath.absolute("/db1/table1"), 3); + } + + @Test + public void executeWcLineCountPrintsCountAndPath() throws SQLException { + when(provider.count(FsPath.absolute("/db1/table1"))).thenReturn(2L); + + assertTrue(shell.execute("wc -l /db1/table1")); + + assertTrue(out.toString().contains("2 /db1/table1")); + verify(provider).count(FsPath.absolute("/db1/table1")); + } + + @Test + public void executeGrepPrintsOnlyMatchingRows() throws SQLException { + when(provider.read(FsPath.absolute("/db1/table1"), 20)) + .thenReturn( + Arrays.asList( + SqlRow.of("Time", "1", "tag1", "spricoder", "s1", "42"), + SqlRow.of("Time", "2", "tag1", "other", "s1", "43"))); + + assertTrue(shell.execute("grep spricoder /db1/table1")); + + assertTrue(out.toString().contains("1\tspricoder\t42")); + assertFalse(out.toString().contains("2\tother\t43")); + verify(provider).read(FsPath.absolute("/db1/table1"), 20); + } + + @Test + public void executeFindPrintsMatchingDescendants() throws SQLException { + when(provider.describe(FsPath.absolute("/"))) + .thenReturn(new FsNode("/", FsPath.absolute("/"), FsNodeType.VIRTUAL_ROOT)); + when(provider.describe(FsPath.absolute("/db1"))) + .thenReturn(new FsNode("db1", FsPath.absolute("/db1"), FsNodeType.TABLE_DATABASE)); + when(provider.describe(FsPath.absolute("/db1/table1"))) + .thenReturn(new FsNode("table1", FsPath.absolute("/db1/table1"), FsNodeType.TABLE_TABLE)); + 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", FsPath.absolute("/db1/table1"), FsNodeType.TABLE_TABLE))); + when(provider.list(FsPath.absolute("/db1/table1"))).thenReturn(new ArrayList<>()); + + assertTrue(shell.execute("find / -name table1")); + + assertTrue(out.toString().contains("/db1/table1")); + verify(provider).list(FsPath.absolute("/")); + verify(provider).list(FsPath.absolute("/db1")); + } + + @Test + public void executeLessAndMoreReadPath() throws SQLException { + when(provider.read(FsPath.absolute("/db1/table1"), 20)) + .thenReturn(Arrays.asList(SqlRow.of("Time", "1", "tag1", "a", "s1", "42"))); + + assertTrue(shell.execute("less /db1/table1")); + assertTrue(shell.execute("more /db1/table1")); + + assertTrue(out.toString().contains("1\ta\t42")); + verify(provider, times(2)).read(FsPath.absolute("/db1/table1"), 20); + } + + @Test + public void executeFilePrintsNodeType() throws SQLException { + when(provider.describe(FsPath.absolute("/db1/table1"))) + .thenReturn(new FsNode("table1", FsPath.absolute("/db1/table1"), FsNodeType.TABLE_TABLE)); + + assertTrue(shell.execute("file /db1/table1")); + + assertTrue(out.toString().contains("/db1/table1: TABLE_TABLE")); + verify(provider).describe(FsPath.absolute("/db1/table1")); + } + + @Test + public void executeDuPrintsLogicalSizeAndPath() throws SQLException { + when(provider.count(FsPath.absolute("/db1/table1"))).thenReturn(2L); + + assertTrue(shell.execute("du /db1/table1")); + + assertTrue(out.toString().contains("2\t/db1/table1")); + verify(provider).count(FsPath.absolute("/db1/table1")); + } + @Test public void executePasteReadsMultiplePaths() throws SQLException { when(provider.read( @@ -231,4 +351,12 @@ public class FilesystemShellTest { completer.complete(null, parsedLine, candidates); return candidates.stream().map(Candidate::value).collect(Collectors.toList()); } + + private CliContext shellContext() { + return new CliContext( + new ByteArrayInputStream(new byte[0]), + new PrintStream(out), + System.err, + ExitType.EXCEPTION); + } } 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 2816912ab0b..7224876d239 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 @@ -87,6 +87,54 @@ public class FilesystemCommandParserTest { assertEquals(5, command.getLimit()); } + @Test + public void parseTailLimitAndPath() { + FilesystemCommand command = FilesystemCommandParser.parse("tail -n 3 /db1/table1"); + + assertEquals(FilesystemCommand.Type.TAIL, command.getType()); + assertEquals("/db1/table1", command.getPath()); + assertEquals(3, command.getLimit()); + } + + @Test + public void parseWcLineCountAndPath() { + FilesystemCommand command = FilesystemCommandParser.parse("wc -l /db1/table1"); + + assertEquals(FilesystemCommand.Type.WC, command.getType()); + assertEquals("/db1/table1", command.getPath()); + assertEquals("-l", command.getOption()); + } + + @Test + public void parseGrepPatternAndPath() { + FilesystemCommand command = FilesystemCommandParser.parse("grep spricoder /db1/table1"); + + assertEquals(FilesystemCommand.Type.GREP, command.getType()); + assertEquals("/db1/table1", command.getPath()); + assertEquals("spricoder", command.getPattern()); + } + + @Test + public void parseFindNamePatternAndPath() { + FilesystemCommand command = FilesystemCommandParser.parse("find /db1 -name table1"); + + assertEquals(FilesystemCommand.Type.FIND, command.getType()); + assertEquals("/db1", command.getPath()); + assertEquals("table1", command.getPattern()); + } + + @Test + public void parseLessMoreFileAndDu() { + assertEquals( + FilesystemCommand.Type.LESS, FilesystemCommandParser.parse("less /db1/table1").getType()); + assertEquals( + FilesystemCommand.Type.MORE, FilesystemCommandParser.parse("more /db1/table1").getType()); + assertEquals( + FilesystemCommand.Type.FILE, FilesystemCommandParser.parse("file /db1/table1").getType()); + assertEquals( + FilesystemCommand.Type.DU, FilesystemCommandParser.parse("du /db1/table1").getType()); + } + @Test public void parsePastePaths() { FilesystemCommand command = @@ -98,6 +146,23 @@ public class FilesystemCommandParserTest { assertEquals("/db1/table1/s1", command.getPaths().get(1)); } + @Test + public void parseWriteCommands() { + FilesystemCommand mkdir = FilesystemCommandParser.parse("mkdir /db1"); + assertEquals(FilesystemCommand.Type.MKDIR, mkdir.getType()); + assertEquals("/db1", mkdir.getPath()); + + FilesystemCommand rm = FilesystemCommandParser.parse("rm /db1/table1"); + assertEquals(FilesystemCommand.Type.RM, rm.getType()); + assertEquals("/db1/table1", rm.getPath()); + + FilesystemCommand mv = FilesystemCommandParser.parse("mv /db1/table1 /db1/table2"); + assertEquals(FilesystemCommand.Type.MV, mv.getType()); + assertEquals(2, mv.getPaths().size()); + assertEquals("/db1/table1", mv.getPaths().get(0)); + assertEquals("/db1/table2", mv.getPaths().get(1)); + } + @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 new file mode 100644 index 00000000000..955046688c4 --- /dev/null +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemMutationProviderTest.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.iotdb.cli.fs.provider; + +import org.apache.iotdb.cli.fs.path.FsPath; +import org.apache.iotdb.cli.fs.sql.SqlExecutor; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.sql.SQLException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Mockito.verify; + +public class TableFilesystemMutationProviderTest { + + @Mock private SqlExecutor executor; + + private TableFilesystemMutationProvider provider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + provider = new TableFilesystemMutationProvider(executor); + } + + @Test + public void mkdirDatabaseCreatesDatabase() throws SQLException { + provider.mkdir(FsPath.absolute("/db1")); + + verify(executor).execute("CREATE DATABASE db1"); + } + + @Test + public void mkdirRejectsRootAndTableLevel() throws SQLException { + assertInvalidOperation(() -> provider.mkdir(FsPath.absolute("/"))); + assertInvalidOperation(() -> provider.mkdir(FsPath.absolute("/db1/table1"))); + } + + @Test + public void removeTableDropsTable() throws SQLException { + provider.remove(FsPath.absolute("/db1/table1")); + + verify(executor).execute("DROP TABLE db1.table1"); + } + + @Test + public void removeRejectsRootDatabaseAndColumnLevel() throws SQLException { + assertInvalidOperation(() -> provider.remove(FsPath.absolute("/"))); + assertInvalidOperation(() -> provider.remove(FsPath.absolute("/db1"))); + assertInvalidOperation(() -> provider.remove(FsPath.absolute("/db1/table1/s1"))); + } + + @Test + public void moveTableRenamesTableInSameDatabase() throws SQLException { + provider.move(FsPath.absolute("/db1/table1"), FsPath.absolute("/db1/table2")); + + verify(executor).execute("ALTER TABLE db1.table1 RENAME TO table2"); + } + + @Test + public void moveRejectsUnsafeLevelsAndCrossDatabaseRename() throws SQLException { + assertInvalidOperation(() -> provider.move(FsPath.absolute("/db1"), FsPath.absolute("/db2"))); + assertInvalidOperation( + () -> provider.move(FsPath.absolute("/db1/table1/s1"), FsPath.absolute("/db1/table1/s2"))); + assertInvalidOperation( + () -> provider.move(FsPath.absolute("/db1/table1"), FsPath.absolute("/db2/table1"))); + } + + private static void assertInvalidOperation(SqlOperation operation) throws SQLException { + try { + operation.run(); + fail(); + } catch (SQLException e) { + assertEquals("Invalid filesystem write operation for this path", e.getMessage()); + } + } + + private interface SqlOperation { + void run() throws SQLException; + } +} diff --git a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProviderTest.java b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProviderTest.java index d846c3b7be8..9d7c1bb6715 100644 --- a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProviderTest.java +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/provider/TableFilesystemSchemaProviderTest.java @@ -171,6 +171,32 @@ public class TableFilesystemSchemaProviderTest { verify(executor).query("SELECT * FROM db1.table1 LIMIT 5"); } + @Test + public void tailTableSelectsNewestRowsAndReturnsOriginalOrder() throws SQLException { + when(executor.query("SELECT * FROM db1.table1 ORDER BY time DESC LIMIT 2")) + .thenReturn( + SqlRow.list( + SqlRow.of("Time", "2", "tag1", "b", "s1", "43"), + SqlRow.of("Time", "1", "tag1", "a", "s1", "42"))); + + List<SqlRow> rows = provider.tail(FsPath.absolute("/db1/table1"), 2); + + assertEquals("1", rows.get(0).get("Time")); + assertEquals("2", rows.get(1).get("Time")); + verify(executor).query("SELECT * FROM db1.table1 ORDER BY time DESC LIMIT 2"); + } + + @Test + public void countTableSelectsRowCount() throws SQLException { + when(executor.query("SELECT COUNT(*) FROM db1.table1")) + .thenReturn(SqlRow.list(SqlRow.of("count", "2"))); + + long count = provider.count(FsPath.absolute("/db1/table1")); + + assertEquals(2L, count); + verify(executor).query("SELECT COUNT(*) FROM db1.table1"); + } + @Test public void readColumnsSelectsMultipleColumnsFromSameTable() throws SQLException { when(executor.query("SELECT tag1, s1 FROM db1.table1 LIMIT 5")) diff --git a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutorTest.java b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutorTest.java index 83f073e30ea..ae405f859a0 100644 --- a/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutorTest.java +++ b/iotdb-client/cli/src/test/java/org/apache/iotdb/cli/fs/sql/JdbcSqlExecutorTest.java @@ -68,4 +68,14 @@ public class JdbcSqlExecutorTest { verify(resultSet).close(); verify(statement).close(); } + + @Test + public void executeRunsStatement() throws Exception { + when(connection.createStatement()).thenReturn(statement); + + new JdbcSqlExecutor(connection).execute("CREATE DATABASE db1"); + + verify(statement).execute("CREATE DATABASE db1"); + verify(statement).close(); + } }
