You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by sp...@apache.org on 2026/05/16 05:47:32 UTC
(iotdb) 01/06: add write
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 <sp...@qq.com>
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();
+ }
}