aboutsummaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/sqlitestudiocli/commands
diff options
context:
space:
mode:
Diffstat (limited to 'SQLiteStudio3/sqlitestudiocli/commands')
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp334
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommand.h97
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp36
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp34
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp51
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp86
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp26
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h16
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp50
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp26
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp85
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h25
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp86
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h19
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp81
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h26
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp65
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h21
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp32
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp84
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp28
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp51
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h15
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp506
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h66
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp79
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h21
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp154
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h31
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp64
-rw-r--r--SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h15
40 files changed, 2445 insertions, 0 deletions
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp
new file mode 100644
index 0000000..3002ee5
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp
@@ -0,0 +1,334 @@
+#include "clicommand.h"
+#include "qio.h"
+#include "cli_config.h"
+#include "cliutils.h"
+#include "common/utils.h"
+#include "common/utils_sql.h"
+#include "clicommandfactory.h"
+#include "services/dbmanager.h"
+#include "schemaresolver.h"
+#include "cli.h"
+
+#include <QDir>
+
+CliCommand::CliCommand()
+{
+}
+
+CliCommand::~CliCommand()
+{
+}
+
+void CliCommand::setup(CLI *cli)
+{
+ this->cli = cli;
+ defineSyntax();
+}
+
+bool CliCommand::isAsyncExecution() const
+{
+ return false;
+}
+
+bool CliCommand::parseArgs(const QStringList& args)
+{
+ bool res = syntax.parse(args);
+
+ if (!res && !syntax.getErrorText().isEmpty())
+ {
+ println(syntax.getErrorText());
+ println();
+ }
+
+ return res;
+}
+
+QString CliCommand::usage() const
+{
+ return syntax.getSyntaxDefinition();
+}
+
+QString CliCommand::usage(const QString& alias) const
+{
+ return syntax.getSyntaxDefinition(alias);
+}
+
+QString CliCommand::getName() const
+{
+ return syntax.getName();
+}
+
+QStringList CliCommand::complete(const QStringList& args)
+{
+ syntax.parse(args.mid(0, args.size() - 1));
+
+ QStringList results;
+ results += syntax.getStrictArgumentCandidates();
+ foreach (int id, syntax.getRegularArgumentCandidates())
+ results += getCompletionValuesFor(id, args.last());
+
+ return results;
+}
+
+QStringList CliCommand::aliases() const
+{
+ return syntax.getAliases();
+}
+
+void CliCommand::println(const QString &str)
+{
+ qOut << str << "\n";
+ qOut.flush();
+}
+
+void CliCommand::print(const QString& str)
+{
+ qOut << str;
+ qOut.flush();
+}
+
+void CliCommand::printBox(const QString& str)
+{
+ int cols = getCliColumns();
+
+ QStringList lines = str.split("\n");
+ println(".---------------------------");
+ foreach (const QString& line, lines)
+ {
+ foreach (const QString& lineWithMargin, applyMargin(line, cols - 3)) // 2 for "| " and 1 for final new line character
+ println("| " + lineWithMargin);
+ }
+
+ println("`---------------------------");
+}
+
+void CliCommand::printUsage()
+{
+ println(tr("Usage: %1%2").arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(usage()));
+ println("");
+}
+
+QString CliCommand::getFilterAndFixDir(QDir& dir, const QString& path)
+{
+ if (path.isEmpty())
+ return "*";
+
+ QString filter;
+ QDir tempDir;
+ tempDir.setPath(path);
+ if (tempDir.exists() && path.endsWith("/"))
+ {
+ dir.cd(path);
+ filter = "*";
+ }
+ else if (tempDir.cdUp())
+ {
+ dir.setPath(path);
+ dir.cdUp();
+ filter = QFileInfo(path).fileName() + "*";
+ }
+ else
+ {
+ filter = path;
+ }
+ return filter;
+}
+
+QStringList CliCommand::getCompletionDbNames()
+{
+ QStringList results = DBLIST->getDbNames();
+ results.sort(Qt::CaseInsensitive);
+ return results;
+}
+
+QStringList CliCommand::getCompletionTables()
+{
+ QStringList results;
+ if (!cli->getCurrentDb())
+ return results;
+
+ Dialect dialect = Dialect::Sqlite3;
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(true);
+ results += wrapObjNamesIfNeeded(resolver.getTables(), dialect);
+ results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTables("temp"), dialect));
+ foreach (const QString& database, resolver.getDatabases())
+ results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTables(database), dialect));
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionIndexes()
+{
+ QStringList results;
+ if (!cli->getCurrentDb())
+ return results;
+
+ Dialect dialect = Dialect::Sqlite3;
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(true);
+ results += wrapObjNamesIfNeeded(resolver.getIndexes(), dialect);
+ results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getIndexes("temp"), dialect));
+ foreach (const QString& database, resolver.getDatabases())
+ results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getIndexes(database), dialect));
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionTriggers()
+{
+ QStringList results;
+ if (!cli->getCurrentDb())
+ return results;
+
+ Dialect dialect = Dialect::Sqlite3;
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(true);
+ results += wrapObjNamesIfNeeded(resolver.getTriggers(), dialect);
+ results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getTriggers("temp"), dialect));
+ foreach (const QString& database, resolver.getDatabases())
+ results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getTriggers(database), dialect));
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionViews()
+{
+ QStringList results;
+ if (!cli->getCurrentDb())
+ return results;
+
+ Dialect dialect = Dialect::Sqlite3;
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(true);
+ results += wrapObjNamesIfNeeded(resolver.getViews(), dialect);
+ results += prefixEach("temp.", wrapObjNamesIfNeeded(resolver.getViews("temp"), dialect));
+ foreach (const QString& database, resolver.getDatabases())
+ results += prefixEach(wrapObjIfNeeded(database, Dialect::Sqlite3)+".", wrapObjNamesIfNeeded(resolver.getViews(database), dialect));
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionDbNamesOrFiles(const QString& partialValue)
+{
+ QStringList results = getCompletionDbNames();
+ results += getCompletionFiles(partialValue);
+ return results;
+}
+
+QStringList CliCommand::getCompletionFiles(const QString& partialValue)
+{
+ QDir dir;
+ QString filter = getFilterAndFixDir(dir, partialValue);
+ QFileInfoList entries = dir.entryInfoList({filter}, QDir::Files, QDir::LocaleAware|QDir::Name);
+
+ QStringList results;
+ QString name;
+ foreach (const QFileInfo& entry, entries)
+ {
+ name = entry.fileName();
+ if (dir != QDir::current())
+ name.prepend(dir.path() + "/");
+
+ results << name;
+ }
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionDirs(const QString& partialValue)
+{
+ QDir dir;
+ QString filter = getFilterAndFixDir(dir, partialValue);
+ QFileInfoList entries = dir.entryInfoList({filter}, QDir::Dirs|QDir::NoDotAndDotDot, QDir::LocaleAware|QDir::Name);
+
+ QStringList results;
+ QString name;
+ foreach (const QFileInfo& entry, entries)
+ {
+ name = entry.fileName();
+ if (dir != QDir::current())
+ name.prepend(dir.path() + "/");
+
+ results << name;
+ }
+
+ return results;
+}
+
+QStringList CliCommand::getCompletionDirsOrFiles(const QString& partialValue)
+{
+ QStringList results = getCompletionDirs(partialValue);
+ results += getCompletionFiles(partialValue);
+ return results;
+}
+
+QStringList CliCommand::getCompletionInternalDbs()
+{
+ QStringList results;
+ if (!cli->getCurrentDb())
+ return results;
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ results += resolver.getDatabases().toList();
+ results << "main" << "temp";
+ results.sort(Qt::CaseInsensitive);
+ return results;
+}
+
+QStringList CliCommand::getCompletionValuesFor(int id, const QString& partialValue)
+{
+ QStringList results;
+ if (id < 1000) // this base implementation is only for local enum values (>= 1000).
+ return results;
+
+ switch (static_cast<ArgIds>(id))
+ {
+ case CliCommand::DB_NAME:
+ results += getCompletionDbNames();
+ break;
+ case CliCommand::DB_NAME_OR_FILE:
+ results += getCompletionDbNamesOrFiles(partialValue);
+ break;
+ case CliCommand::FILE_PATH:
+ results += getCompletionFiles(partialValue);
+ break;
+ case CliCommand::DIR_PATH:
+ results += getCompletionDirs(partialValue);
+ break;
+ case CliCommand::CMD_NAME:
+ results += CliCommandFactory::getCommandNames();
+ break;
+ case CliCommand::DIR_OR_FILE:
+ results += getCompletionDirsOrFiles(partialValue);
+ break;
+ case CliCommand::INTRNAL_DB:
+ results += getCompletionInternalDbs();
+ break;
+ case CliCommand::TABLE:
+ results += getCompletionTables();
+ break;
+ case CliCommand::INDEX:
+ results += getCompletionIndexes();
+ break;
+ case CliCommand::TRIGGER:
+ results += getCompletionTriggers();
+ break;
+ case CliCommand::VIEW:
+ results += getCompletionViews();
+ break;
+ case CliCommand::STRING:
+ break;
+ }
+
+ return results;
+}
+
+QString CliCommand::cmdName(const QString& cmd)
+{
+ return CFG_CLI.Console.CommandPrefixChar.get()+cmd;
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h
new file mode 100644
index 0000000..4fc8618
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommand.h
@@ -0,0 +1,97 @@
+#ifndef CLICOMMAND_H
+#define CLICOMMAND_H
+
+#include "clicommandsyntax.h"
+#include <QStringList>
+#include <QTextStream>
+#include <QObject>
+#include <QDir>
+
+class QFile;
+class SQLiteStudio;
+class DbManager;
+class CLI;
+class Config;
+
+class CliCommand : public QObject
+{
+ Q_OBJECT
+
+ public:
+ CliCommand();
+ virtual ~CliCommand();
+
+ void setup(CLI* cli);
+
+ /**
+ * @brief execute
+ */
+ virtual void execute() = 0;
+
+ /**
+ * @brief Short help displayed in commands index.
+ * @return Single line of command description.
+ */
+ virtual QString shortHelp() const = 0;
+
+ /**
+ * @brief Full help is displayed when help for specific command was requested.
+ * @return Multi line, detailed description, including syntax.
+ */
+ virtual QString fullHelp() const = 0;
+
+ virtual bool isAsyncExecution() const;
+
+ virtual void defineSyntax() = 0;
+
+ QStringList aliases() const;
+ bool parseArgs(const QStringList& args);
+ QString usage() const;
+ QString usage(const QString& alias) const;
+ QString getName() const;
+ QStringList complete(const QStringList& args);
+
+ protected:
+ enum ArgIds
+ {
+ DB_NAME = 1000,
+ DB_NAME_OR_FILE = 1001,
+ FILE_PATH = 1002,
+ DIR_OR_FILE = 1003,
+ DIR_PATH = 1004,
+ CMD_NAME = 1005,
+ INTRNAL_DB = 1006,
+ STRING = 1007,
+ TABLE = 1008,
+ INDEX = 1009,
+ TRIGGER = 1010,
+ VIEW = 1011
+ };
+
+ static void println(const QString& str = "");
+ static void print(const QString& str = "");
+ static void printBox(const QString& str);
+ static QString cmdName(const QString& cmd);
+
+ void printUsage();
+ QString getFilterAndFixDir(QDir& dir, const QString& path);
+ QStringList getCompletionDbNames();
+ QStringList getCompletionTables();
+ QStringList getCompletionIndexes();
+ QStringList getCompletionTriggers();
+ QStringList getCompletionViews();
+ QStringList getCompletionDbNamesOrFiles(const QString& partialValue);
+ QStringList getCompletionFiles(const QString& partialValue);
+ QStringList getCompletionDirs(const QString& partialValue);
+ QStringList getCompletionDirsOrFiles(const QString& partialValue);
+ QStringList getCompletionInternalDbs();
+ virtual QStringList getCompletionValuesFor(int id, const QString& partialValue);
+
+ CLI* cli = nullptr;
+ CliCommandSyntax syntax;
+
+ signals:
+ void execComplete();
+};
+
+#endif // CLICOMMAND_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp
new file mode 100644
index 0000000..e3c14bb
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp
@@ -0,0 +1,36 @@
+#include "clicommandadd.h"
+#include "cli.h"
+#include "services/dbmanager.h"
+
+void CliCommandAdd::execute()
+{
+ if (!DBLIST->addDb(syntax.getArgument(DB_NAME), syntax.getArgument(FILE_PATH)))
+ {
+ println(tr("Could not add database %1 to list.").arg(syntax.getArgument(FILE_PATH)));
+ return;
+ }
+
+ cli->setCurrentDb(DBLIST->getByName(syntax.getArgument(DB_NAME)));
+ println(tr("Database added: %1").arg(cli->getCurrentDb()->getName()));
+}
+
+QString CliCommandAdd::shortHelp() const
+{
+ return tr("adds new database to the list");
+}
+
+QString CliCommandAdd::fullHelp() const
+{
+ return tr(
+ "Adds given database pointed by <path> with given <name> to list the databases list. "
+ "The <name> is just a symbolic name that you can later refer to. Just pick any unique name. "
+ "For list of databases already on the list use %1 command."
+ ).arg(cmdName("dblist"));
+}
+
+void CliCommandAdd::defineSyntax()
+{
+ syntax.setName("add");
+ syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"));
+ syntax.addArgument(FILE_PATH, tr("path", "CLI command syntax"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h
new file mode 100644
index 0000000..ae09ee1
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDADD_H
+#define CLICOMMANDADD_H
+
+#include "clicommand.h"
+
+class CliCommandAdd : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDADD_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp
new file mode 100644
index 0000000..0ca6cb6
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp
@@ -0,0 +1,34 @@
+#include "clicommandcd.h"
+#include <QCoreApplication>
+#include <QDir>
+
+void CliCommandCd::execute()
+{
+ QDir dir;
+ dir.cd(syntax.getArgument(DIR_PATH));
+ if (QDir::setCurrent(dir.absolutePath()))
+ println(tr("Changed directory to: %1").arg(QDir::currentPath()));
+ else
+ println(tr("Could not change directory to: %1").arg(QDir::currentPath()));
+}
+
+QString CliCommandCd::shortHelp() const
+{
+ return tr("changes current workind directory");
+}
+
+QString CliCommandCd::fullHelp() const
+{
+ return tr(
+ "Very similar command to 'cd' known from Unix systems and Windows. "
+ "It requires a <path> argument to be passed, therefore calling %1 will always cause a change of the directory. "
+ "To learn what's the current working directory use %2 command and to list contents of the current working directory "
+ "use %3 command."
+ );
+}
+
+void CliCommandCd::defineSyntax()
+{
+ syntax.setName("cd");
+ syntax.addArgument(DIR_PATH, tr("path", "CLI command syntax"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h
new file mode 100644
index 0000000..4cfabc9
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDCD_H
+#define CLICOMMANDCD_H
+
+#include "clicommand.h"
+
+class CliCommandCd : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDCD_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp
new file mode 100644
index 0000000..44fc72c
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp
@@ -0,0 +1,51 @@
+#include "clicommandclose.h"
+#include "cli.h"
+#include "db/db.h"
+#include "services/dbmanager.h"
+
+void CliCommandClose::execute()
+{
+ if (!syntax.isArgumentSet(DB_NAME) && !cli->getCurrentDb())
+ {
+ println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.")
+ .arg(cmdName("close")).arg(cmdName("use")).arg(cmdName("close")));
+ return;
+ }
+
+ if (syntax.isArgumentSet(DB_NAME))
+ {
+ Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME));
+ if (db)
+ {
+ db->close();
+ println(tr("Connection to database %1 closed.").arg(db->getName()));
+ }
+ else
+ println(tr("No such database: %1. Use %2 to see list of known databases.").arg(syntax.getArgument(DB_NAME)).arg(cmdName("dblist")));
+ }
+ else if (cli->getCurrentDb())
+ {
+ cli->getCurrentDb()->close();
+ println(tr("Connection to database %1 closed.").arg(cli->getCurrentDb()->getName()));
+ }
+}
+
+QString CliCommandClose::shortHelp() const
+{
+ return tr("closes given (or current) database");
+}
+
+QString CliCommandClose::fullHelp() const
+{
+ return tr(
+ "Closes database connection. If the database was already closed, nothing happens. "
+ "If <name> is provided, it should be name of the database to close (as printed by %1 command). "
+ "The the <name> is not provided, then current working database is closed (see help for %2 for details)."
+ ).arg(cmdName("dblist")).arg(cmdName("use"));
+}
+
+void CliCommandClose::defineSyntax()
+{
+ syntax.setName("close");
+ syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h
new file mode 100644
index 0000000..04fbbeb
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDCLOSE_H
+#define CLICOMMANDCLOSE_H
+
+#include "clicommand.h"
+
+class CliCommandClose : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDCLOSE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp
new file mode 100644
index 0000000..cbc16b6
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp
@@ -0,0 +1,86 @@
+#include "clicommanddblist.h"
+#include "cli.h"
+#include "services/dbmanager.h"
+#include "common/unused.h"
+#include "common/utils.h"
+#include <QList>
+
+void CliCommandDbList::execute()
+{
+ if (!cli->getCurrentDb())
+ {
+ println(tr("No current working database defined."));
+ return;
+ }
+
+ QString currentName = cli->getCurrentDb()->getName();
+
+ println(tr("Databases:"));
+ QList<Db*> dbList = DBLIST->getDbList();
+ QString path;
+ QString msg;
+
+ int maxNameLength = tr("Name", "CLI db name column").length();
+ int lgt = 0;
+ foreach (Db* db, dbList)
+ {
+ lgt = db->getName().length() + 1;
+ maxNameLength = qMax(maxNameLength, lgt);
+ }
+
+ int connStateLength = qMax(tr("Open", "CLI connection state column").length(), tr("Closed", "CLI connection state column").length());
+ connStateLength = qMax(connStateLength, tr("Connection", "CLI connection state column").length());
+
+ msg = pad(tr("Name", "CLI db name column"), maxNameLength, ' ');
+ msg += "|";
+ msg += pad(tr("Connection", "CLI connection state column"), connStateLength, ' ');
+ msg += "|";
+ msg += tr("Database file path");
+ println(msg);
+
+ msg = QString("-").repeated(maxNameLength);
+ msg += "+";
+ msg += QString("-").repeated(connStateLength);
+ msg += "+";
+ msg += QString("-").repeated(tr("Database file path").length() + 1);
+ println(msg);
+
+ QString name;
+ foreach (Db* db, dbList)
+ {
+ bool open = db->isOpen();
+ path = db->getPath();
+ name = db->getName();
+ if (name == currentName)
+ name.prepend("*");
+ else
+ name.prepend(" ");
+
+ msg = pad(name, maxNameLength, ' ');
+ msg += "|";
+ msg += pad((open ? tr("Open") : tr("Closed")), connStateLength, ' ');
+ msg += "|";
+ msg += path;
+ println(msg);
+ }
+}
+
+QString CliCommandDbList::shortHelp() const
+{
+ return tr("prints list of registered databases");
+}
+
+QString CliCommandDbList::fullHelp() const
+{
+ return tr(
+ "Prints list of databases registered in the SQLiteStudio. Each database on the list can be in open or closed state "
+ "and .dblist tells you that. The current working database (aka default database) is also marked on the list with '*' at the start of its name. "
+ "See help for %1 command to learn about the default database."
+ ).arg(cmdName("use"));
+}
+
+void CliCommandDbList::defineSyntax()
+{
+ syntax.setName("dblist");
+ syntax.addAlias("databases");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h
new file mode 100644
index 0000000..ecbaa6f
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDDBLIST_H
+#define CLICOMMANDDBLIST_H
+
+#include "clicommand.h"
+
+class CliCommandDbList : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDDBLIST_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp
new file mode 100644
index 0000000..4865e23
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp
@@ -0,0 +1,26 @@
+#include "clicommanddesc.h"
+
+CliCommandDesc::CliCommandDesc()
+{
+}
+
+void CliCommandDesc::execute()
+{
+
+}
+
+QString CliCommandDesc::shortHelp() const
+{
+ return tr("shows details about the table");
+}
+
+QString CliCommandDesc::fullHelp() const
+{
+ return QString();
+}
+
+void CliCommandDesc::defineSyntax()
+{
+ syntax.setName("desc");
+ syntax.addArgument(TABLE, tr("table"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h
new file mode 100644
index 0000000..ee52c30
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h
@@ -0,0 +1,16 @@
+#ifndef CLICOMMANDDESC_H
+#define CLICOMMANDDESC_H
+
+#include "clicommand.h"
+
+class CliCommandDesc : public CliCommand
+{
+ public:
+ CliCommandDesc();
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDDESC_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp
new file mode 100644
index 0000000..c61f503
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp
@@ -0,0 +1,50 @@
+#include "clicommanddir.h"
+#include <QDir>
+
+void CliCommandDir::execute()
+{
+ QDir dir;
+ QFileInfoList entries;
+
+ if (syntax.isArgumentSet(DIR_OR_FILE))
+ {
+ QString filter = getFilterAndFixDir(dir, syntax.getArgument(DIR_OR_FILE));
+ entries = dir.entryInfoList({filter}, QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name);
+ }
+ else
+ entries = dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot, QDir::DirsFirst|QDir::LocaleAware|QDir::Name);
+
+ QString name;
+ foreach (const QFileInfo& entry, entries)
+ {
+ name = entry.fileName();
+ if (entry.isDir())
+ name += "/";
+
+ if (dir != QDir::current())
+ name.prepend(dir.path()+"/");
+
+ println(name);
+ }
+}
+
+QString CliCommandDir::shortHelp() const
+{
+ return tr("lists directories and files in current working directory");
+}
+
+QString CliCommandDir::fullHelp() const
+{
+ return tr(
+ "This is very similar to 'dir' command known from Windows and 'ls' command from Unix systems.\n"
+ "\n"
+ "You can pass <pattern> with wildcard characters to filter output."
+ );
+}
+
+void CliCommandDir::defineSyntax()
+{
+ syntax.setName("dir");
+ syntax.addAlias("ls");
+ syntax.addArgument(DIR_OR_FILE, tr("pattern"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h
new file mode 100644
index 0000000..8aa5ccc
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDDIR_H
+#define CLICOMMANDDIR_H
+
+#include "clicommand.h"
+
+class CliCommandDir : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDDIR_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp
new file mode 100644
index 0000000..2c45e1d
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp
@@ -0,0 +1,26 @@
+#include "clicommandexit.h"
+#include "cli.h"
+#include "common/unused.h"
+
+void CliCommandExit::execute()
+{
+ cli->exit();
+}
+
+QString CliCommandExit::shortHelp() const
+{
+ return tr("quits the application");
+}
+
+QString CliCommandExit::fullHelp() const
+{
+ return tr(
+ "Quits the application. Settings are stored in configuration file and will be restored on next startup."
+ );
+}
+
+void CliCommandExit::defineSyntax()
+{
+ syntax.setName("exit");
+ syntax.addAlias("quit");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h
new file mode 100644
index 0000000..dc061bb
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDEXIT_H
+#define CLICOMMANDEXIT_H
+
+#include "clicommand.h"
+
+class CliCommandExit : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDEXIT_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp
new file mode 100644
index 0000000..742709b
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp
@@ -0,0 +1,85 @@
+#include "clicommandfactory.h"
+#include "cli.h"
+#include "clicommandadd.h"
+#include "clicommandremove.h"
+#include "clicommandexit.h"
+#include "clicommanddblist.h"
+#include "clicommanduse.h"
+#include "clicommandopen.h"
+#include "clicommandclose.h"
+#include "clicommandsql.h"
+#include "clicommandhelp.h"
+#include "clicommandtables.h"
+#include "clicommandmode.h"
+#include "clicommandnullvalue.h"
+#include "clicommandhistory.h"
+#include "clicommanddir.h"
+#include "clicommandpwd.h"
+#include "clicommandcd.h"
+#include "clicommandtree.h"
+#include "clicommanddesc.h"
+#include <QDebug>
+
+QHash<QString,CliCommandFactory::CliCommandCreatorFunc> CliCommandFactory::mapping;
+
+#define REGISTER_CMD(Cmd) registerCommand([]() -> CliCommand* {return new Cmd();})
+
+void CliCommandFactory::init()
+{
+ REGISTER_CMD(CliCommandAdd);
+ REGISTER_CMD(CliCommandRemove);
+ REGISTER_CMD(CliCommandExit);
+ REGISTER_CMD(CliCommandDbList);
+ REGISTER_CMD(CliCommandUse);
+ REGISTER_CMD(CliCommandOpen);
+ REGISTER_CMD(CliCommandClose);
+ REGISTER_CMD(CliCommandSql);
+ REGISTER_CMD(CliCommandHelp);
+ REGISTER_CMD(CliCommandTables);
+ REGISTER_CMD(CliCommandMode);
+ REGISTER_CMD(CliCommandNullValue);
+ REGISTER_CMD(CliCommandHistory);
+ REGISTER_CMD(CliCommandDir);
+ REGISTER_CMD(CliCommandPwd);
+ REGISTER_CMD(CliCommandCd);
+ REGISTER_CMD(CliCommandTree);
+ REGISTER_CMD(CliCommandDesc);
+}
+
+CliCommand *CliCommandFactory::getCommand(const QString &cmdName)
+{
+ if (!mapping.contains(cmdName))
+ return nullptr;
+
+ return mapping[cmdName]();
+}
+
+QHash<QString,CliCommand*> CliCommandFactory::getAllCommands()
+{
+ QHash<QString,CliCommand*> results;
+ QHashIterator<QString,CliCommandFactory::CliCommandCreatorFunc> it(mapping);
+ while (it.hasNext())
+ {
+ it.next();
+ results[it.key()] = it.value()();
+ }
+
+ return results;
+}
+
+QStringList CliCommandFactory::getCommandNames()
+{
+ return mapping.keys();
+}
+
+void CliCommandFactory::registerCommand(CliCommandCreatorFunc func)
+{
+ CliCommand* cmd = func();
+ cmd->defineSyntax();
+
+ mapping[cmd->getName()] = func;
+ foreach (const QString& alias, cmd->aliases())
+ mapping[alias] = func;
+
+ delete cmd;
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h
new file mode 100644
index 0000000..4329463
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h
@@ -0,0 +1,25 @@
+#ifndef CLICOMMANDFACTORY_H
+#define CLICOMMANDFACTORY_H
+
+#include <QString>
+#include <QHash>
+
+class CliCommand;
+
+class CliCommandFactory
+{
+ public:
+ static void init();
+ static CliCommand* getCommand(const QString& cmdName);
+ static QHash<QString,CliCommand*> getAllCommands();
+ static QStringList getCommandNames();
+
+ private:
+ typedef CliCommand* (*CliCommandCreatorFunc)();
+
+ static void registerCommand(CliCommandCreatorFunc func);
+
+ static QHash<QString,CliCommandCreatorFunc> mapping;
+};
+
+#endif // CLICOMMANDFACTORY_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp
new file mode 100644
index 0000000..7a957c3
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp
@@ -0,0 +1,86 @@
+#include "clicommandhelp.h"
+#include "clicommandfactory.h"
+#include "common/utils.h"
+#include "cli_config.h"
+
+void CliCommandHelp::execute()
+{
+ if (syntax.isArgumentSet(CMD_NAME))
+ printHelp(syntax.getArgument(CMD_NAME));
+ else
+ printHelp();
+}
+
+QString CliCommandHelp::shortHelp() const
+{
+ return tr("shows this help message");
+}
+
+QString CliCommandHelp::fullHelp() const
+{
+ return tr(
+ "Use %1 to learn about certain commands supported by the command line interface (CLI) of the SQLiteStudio.\n"
+ "To see list of supported commands, type %2 without any arguments.\n\n"
+ "When passing <command> name, you can skip special prefix character ('%3').\n\n"
+ "You can always execute any command with exactly single '--help' option to see help for that command. "
+ "It's an alternative for typing: %1 <command>."
+ ).arg(cmdName("help")).arg(cmdName("help")).arg(CFG_CLI.Console.CommandPrefixChar.get()).arg(cmdName("help"));
+}
+
+void CliCommandHelp::defineSyntax()
+{
+ syntax.setName("help");
+ syntax.addArgument(CMD_NAME, tr("command", "CLI command syntax"), false);
+}
+
+void CliCommandHelp::printHelp(const QString& cmd)
+{
+ QString cmdStr = cmd.startsWith(".") ? cmd.mid(1) : cmd;
+ CliCommand* command = CliCommandFactory::getCommand(cmdStr);
+ if (!command)
+ {
+ println(tr("No such command: %1").arg(cmd));
+ println(tr("Type '%1' for list of available commands.").arg(cmdName("help")));
+ println("");
+ return;
+ }
+ command->defineSyntax();
+ QStringList aliases = command->aliases();
+ QString prefix = CFG_CLI.Console.CommandPrefixChar.get();
+
+ QString msg;
+ msg += tr("Usage: %1%2").arg(prefix).arg(command->usage(cmdStr));
+ msg += "\n";
+ if (aliases.size() > 0)
+ {
+ if (aliases.contains(cmdStr))
+ {
+ aliases.removeOne(cmdStr);
+ aliases << command->getName();
+ }
+
+ msg += tr("Aliases: %1").arg(prefix + aliases.join(", " + prefix));
+ msg += "\n";
+ }
+ msg += "\n";
+ msg += command->fullHelp();
+ delete command;
+
+ printBox(msg);
+}
+
+void CliCommandHelp::printHelp()
+{
+ QHash<QString, CliCommand*> allCommands = CliCommandFactory::getAllCommands();
+ QStringList names = allCommands.keys();
+ int width = longest(names).size();
+
+ names.sort();
+ QStringList msgList;
+ foreach (const QString& cmd, names)
+ {
+ msgList << (CFG_CLI.Console.CommandPrefixChar.get() + pad(cmd, width, ' ') + " - " + allCommands[cmd]->shortHelp());
+ delete allCommands[cmd];
+ }
+ printBox(msgList.join("\n"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h
new file mode 100644
index 0000000..c12c588
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h
@@ -0,0 +1,19 @@
+#ifndef CLICOMMANDHELP_H
+#define CLICOMMANDHELP_H
+
+#include "clicommand.h"
+
+class CliCommandHelp : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ void printHelp(const QString& cmd);
+ void printHelp();
+};
+
+#endif // CLICOMMANDHELP_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp
new file mode 100644
index 0000000..fd2a494
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp
@@ -0,0 +1,81 @@
+#include "clicommandhistory.h"
+#include "cli.h"
+#include "common/utils.h"
+#include "cliutils.h"
+#include "services/config.h"
+
+void CliCommandHistory::execute()
+{
+ if (syntax.isOptionSet(OPER_TYPE))
+ {
+ clear();
+ return;
+ }
+
+ if (syntax.isOptionSet(HIST_LIMIT))
+ {
+ setMax(syntax.getOptionValue(HIST_LIMIT));
+ return;
+ }
+
+ if (syntax.isOptionSet(SHOW_LIMIT))
+ {
+ println(tr("Current history limit is set to: %1").arg(CFG_CORE.Console.HistorySize.get()));
+ return;
+ }
+
+ int cols = getCliColumns();
+ QString hline = pad("", cols, '-');
+ foreach (const QString& line, cli->getHistory())
+ {
+ print(hline);
+ println(line);
+ }
+ println(hline);
+}
+
+QString CliCommandHistory::shortHelp() const
+{
+ return tr("prints history or erases it");
+}
+
+QString CliCommandHistory::fullHelp() const
+{
+ return tr(
+ "When no argument was passed, this command prints command line history. "
+ "Every history entry is separated with a horizontal line, so multiline entries are easier to read.\n"
+ "\n"
+ "When the -c or --clear option is passed, then the history gets erased.\n"
+ "When the -l or --limit option is passed, it sets the new history entries limit. It requires an additional argument"
+ "saying how many entries do you want the history to be limited to.\n"
+ "Use -ql or --querylimit option to see the current limit value."
+ );
+}
+
+void CliCommandHistory::defineSyntax()
+{
+ syntax.setName("history");
+ syntax.addOption(OPER_TYPE, "c", "clear");
+ syntax.addOptionWithArg(HIST_LIMIT, "l", "limit", tr("number"));
+ syntax.addOption(SHOW_LIMIT, "ql", "querylimit");
+}
+
+void CliCommandHistory::clear()
+{
+ cli->clearHistory();
+ println(tr("Console history erased."));
+}
+
+void CliCommandHistory::setMax(const QString& arg)
+{
+ bool ok;
+ int max = arg.toInt(&ok);
+ if (!ok)
+ {
+ println(tr("Invalid number: %1").arg(arg));
+ return;
+ }
+ CFG_CORE.Console.HistorySize.set(max);
+ cli->applyHistoryLimit();
+ println(tr("History limit set to %1").arg(max));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h
new file mode 100644
index 0000000..f2e07d0
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h
@@ -0,0 +1,26 @@
+#ifndef CLICOMMANDHISTORY_H
+#define CLICOMMANDHISTORY_H
+
+#include "clicommand.h"
+
+class CliCommandHistory : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIds
+ {
+ OPER_TYPE,
+ HIST_LIMIT,
+ SHOW_LIMIT
+ };
+
+ void clear();
+ void setMax(const QString& arg);
+};
+
+#endif // CLICOMMANDHISTORY_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp
new file mode 100644
index 0000000..b258045
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp
@@ -0,0 +1,65 @@
+#include "clicommandmode.h"
+#include "common/unused.h"
+#include "cli_config.h"
+
+void CliCommandMode::execute()
+{
+ if (!syntax.isArgumentSet(MODE))
+ {
+ println(tr("Current results printing mode: %1").arg(CliResultsDisplay::mode(CFG_CLI.Console.ResultsDisplayMode.get())));
+ return;
+ }
+
+ CliResultsDisplay::Mode mode = CliResultsDisplay::mode(syntax.getArgument(MODE).toUpper());
+ if (syntax.getArgument(MODE).toUpper() != CliResultsDisplay::mode(mode))
+ {
+ println(tr("Invalid results printing mode: %1").arg(syntax.getArgument(MODE).toUpper()));
+ return;
+ }
+
+ CFG_CLI.Console.ResultsDisplayMode.set(mode);
+ println(tr("New results printing mode: %1").arg(CliResultsDisplay::mode(mode)));
+}
+
+QString CliCommandMode::shortHelp() const
+{
+ return tr("tells or changes the query results format");
+}
+
+QString CliCommandMode::fullHelp() const
+{
+ return tr(
+ "When called without argument, tells the current output format for a query results. "
+ "When the <mode> is passed, the mode is changed to the given one. "
+ "Supported modes are:\n"
+ "- CLASSIC - columns are separated by a comma, not aligned,\n"
+ "- FIXED - columns have equal and fixed width, they always fit into terminal window width, but the data in columns can be cut off,\n"
+ "- COLUMNS - like FIXED, but smarter (do not use with huge result sets, see details below),\n"
+ "- ROW - each column from the row is displayed in new line, so the full data is displayed.\n"
+ "\n"
+ "The CLASSIC mode is recommended if you want to see all the data, but you don't want to waste lines for each column. "
+ "Each row will display full data for every column, but this also means, that columns will not be aligned to each other in next rows. "
+ "The CLASSIC mode also doesn't respect the width of your terminal (console) window, so if values in columns are wider than the window, "
+ "the row will be continued in next lines.\n"
+ "\n"
+ "The FIXED mode is recommended if you want a readable output and you don't care about long data values. "
+ "Columns will be aligned, making the output a nice table. The width of columns is calculated from width of the console window "
+ "and a number of columns.\n"
+ "\n"
+ "The COLUMNS mode is similar to FIXED mode, except it tries to be smart and make columns with shorter values more thin, "
+ "while columns with longer values get more space. First to shrink are columns with longest headers (so the header names are to be "
+ "cut off as first), then columns with the longest values are shrinked, up to the moment when all columns fit into terminal window.\n"
+ "ATTENTION! The COLUMNS mode reads all the results from the query at once in order to evaluate column widhts, therefore it is dangerous "
+ "to use this mode when working with huge result sets. Keep in mind that this mode will load entire result set into memory.\n"
+ "\n"
+ "The ROW mode is recommended if you need to see whole values and you don't expect many rows to be displayed, because this mode "
+ "displays a line of output per each column, so you'll get 10 lines for single row with 10 columns, then if you have 10 of such rows, "
+ "you will get 100 lines of output (+1 extra line per each row, to separate rows from each other)."
+ );
+}
+
+void CliCommandMode::defineSyntax()
+{
+ syntax.setName("mode");
+ syntax.addStrictArgument(MODE, {"classic", "fixed", "columns", "row"}, false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h
new file mode 100644
index 0000000..2b087d3
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h
@@ -0,0 +1,21 @@
+#ifndef CLICOMMANDMODE_H
+#define CLICOMMANDMODE_H
+
+#include "clicommand.h"
+
+class CliCommandMode : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIgs
+ {
+ MODE
+ };
+};
+
+#endif // CLICOMMANDMODE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp
new file mode 100644
index 0000000..9616d9e
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp
@@ -0,0 +1,32 @@
+#include "clicommandnullvalue.h"
+#include "cli_config.h"
+
+void CliCommandNullValue::execute()
+{
+ if (syntax.isArgumentSet(STRING))
+ CFG_CLI.Console.NullValue.set(syntax.getArgument(STRING));
+
+ println(tr("Current NULL representation string: %1").arg(CFG_CLI.Console.NullValue.get()));
+ return;
+}
+
+QString CliCommandNullValue::shortHelp() const
+{
+ return tr("tells or changes the NULL representation string");
+}
+
+QString CliCommandNullValue::fullHelp() const
+{
+ return tr(
+ "If no argument was passed, it tells what's the current NULL value representation "
+ "(that is - what is printed in place of NULL values in query results). "
+ "If the argument is given, then it's used as a new string to be used for NULL representation."
+ );
+}
+
+void CliCommandNullValue::defineSyntax()
+{
+ syntax.setName("null");
+ syntax.addAlias("nullvalue");
+ syntax.addArgument(STRING, QObject::tr("string", "CLI command syntax"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h
new file mode 100644
index 0000000..4ac4699
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDNULLVALUE_H
+#define CLICOMMANDNULLVALUE_H
+
+#include "clicommand.h"
+
+class CliCommandNullValue : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDNULLVALUE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp
new file mode 100644
index 0000000..fef1737
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp
@@ -0,0 +1,84 @@
+#include "clicommandopen.h"
+#include "cli.h"
+#include "services/dbmanager.h"
+#include <QFile>
+#include <QDir>
+#include <QDebug>
+
+void CliCommandOpen::execute()
+{
+ if (!syntax.isArgumentSet(DB_NAME_OR_FILE) && !cli->getCurrentDb())
+ {
+ println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.")
+ .arg(cmdName("open")).arg(cmdName("use")).arg(cmdName("open")));
+ return;
+ }
+
+ Db* db = nullptr;
+ if (syntax.isArgumentSet(DB_NAME_OR_FILE))
+ {
+ QString arg = syntax.getArgument(DB_NAME_OR_FILE);
+ db = DBLIST->getByName(arg);
+ if (!db)
+ {
+ if (QFile::exists(arg))
+ {
+ QString newName = DbManager::generateDbName(arg);
+ if (!DBLIST->addDb(newName, arg, false))
+ {
+ println(tr("Could not add database %1 to list.").arg(arg));
+ return;
+ }
+ db = DBLIST->getByName(arg);
+ Q_ASSERT(db != nullptr);
+ }
+ else
+ {
+ println(tr("File %1 doesn't exist in %2. Cannot open inexisting database with %3 command. "
+ "To create a new database, use %4 command.").arg(arg).arg(QDir::currentPath())
+ .arg(cmdName("open")).arg(cmdName("add")));
+ return;
+ }
+ }
+ }
+ else
+ {
+ db = cli->getCurrentDb();
+ if (!db)
+ {
+ qCritical() << "Default database is not in the list!";
+ return ;
+ }
+ }
+
+ if (!db->open())
+ {
+ println(db->getErrorText());
+ return;
+ }
+
+ cli->setCurrentDb(db);
+ println(tr("Database %1 has been open and set as the current working database.").arg(db->getName()));
+}
+
+QString CliCommandOpen::shortHelp() const
+{
+ return tr("opens database connection");
+}
+
+QString CliCommandOpen::fullHelp() const
+{
+ return tr(
+ "Opens connection to the database. If no additional argument was passed, then the connection is open to the "
+ "current default database (see help for %1 for details). However if an argument was passed, it can be either "
+ "<name> of the registered database to open, or it can be <path> to the database file to open. "
+ "In the second case, the <path> gets registered on the list with a generated name, but only for the period "
+ "of current application session. After restarting application such database is not restored on the list."
+ ).arg(cmdName("use"));
+}
+
+void CliCommandOpen::defineSyntax()
+{
+ syntax.setName("open");
+ syntax.addAlternatedArgument(DB_NAME_OR_FILE, {tr("name", "CLI command syntax"), tr("path", "CLI command syntax")}, false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h
new file mode 100644
index 0000000..e66f574
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDOPEN_H
+#define CLICOMMANDOPEN_H
+
+#include "clicommand.h"
+
+class CliCommandOpen : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDOPEN_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp
new file mode 100644
index 0000000..f96cae4
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp
@@ -0,0 +1,28 @@
+#include "clicommandpwd.h"
+#include "common/unused.h"
+#include <QDir>
+
+void CliCommandPwd::execute()
+{
+ QDir dir;
+ println(dir.absolutePath());
+}
+
+QString CliCommandPwd::shortHelp() const
+{
+ return tr("prints the current working directory");
+}
+
+QString CliCommandPwd::fullHelp() const
+{
+ return tr(
+ "This is the same as 'pwd' command on Unix systems and 'cd' command without arguments on Windows. "
+ "It prints current working directory. You can change the current working directory with %1 command "
+ "and you can also list contents of the current working directory with %2 command."
+ ).arg(cmdName("cd")).arg(cmdName("dir"));
+}
+
+void CliCommandPwd::defineSyntax()
+{
+ syntax.setName("pwd");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h
new file mode 100644
index 0000000..3568ce1
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDPWD_H
+#define CLICOMMANDPWD_H
+
+#include "clicommand.h"
+
+class CliCommandPwd : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDPWD_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp
new file mode 100644
index 0000000..6474a78
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp
@@ -0,0 +1,51 @@
+#include "clicommandremove.h"
+#include "cli.h"
+#include "services/dbmanager.h"
+#include "db/db.h"
+
+void CliCommandRemove::execute()
+{
+ QString dbName = syntax.getArgument(DB_NAME);
+ Db* db = DBLIST->getByName(dbName);
+ if (!db)
+ {
+ println(tr("No such database: %1").arg(dbName));
+ return;
+ }
+
+ bool isCurrent = cli->getCurrentDb() == db;
+ QString name = db->getName();
+
+ DBLIST->removeDb(db);
+ println(tr("Database removed: %1").arg(name));
+
+ QList<Db*> dblist = DBLIST->getDbList();
+ if (isCurrent && dblist.size() > 0)
+ {
+ cli->setCurrentDb(dblist[0]);
+ println(tr("New current database set:"));
+ println(cli->getCurrentDb()->getName());
+ }
+ else
+ cli->setCurrentDb(nullptr);
+}
+
+QString CliCommandRemove::shortHelp() const
+{
+ return tr("removes database from the list");
+}
+
+QString CliCommandRemove::fullHelp() const
+{
+ return tr(
+ "Removes <name> database from the list of registered databases. "
+ "If the database was not on the list (see %1 command), then error message is printed "
+ "and nothing more happens."
+ ).arg(cmdName("dblist"));
+}
+
+void CliCommandRemove::defineSyntax()
+{
+ syntax.setName("remove");
+ syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"));
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h
new file mode 100644
index 0000000..f4b5402
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDREMOVE_H
+#define CLICOMMANDREMOVE_H
+
+#include "clicommand.h"
+
+class CliCommandRemove : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDREMOVE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp
new file mode 100644
index 0000000..3f2c4b3
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp
@@ -0,0 +1,506 @@
+#include "clicommandsql.h"
+#include "cli.h"
+#include "parser/ast/sqliteselect.h"
+#include "parser/parser.h"
+#include "parser/parsererror.h"
+#include "db/queryexecutor.h"
+#include "qio.h"
+#include "common/unused.h"
+#include "cli_config.h"
+#include "cliutils.h"
+#include <QList>
+#include <QDebug>
+
+void CliCommandSql::execute()
+{
+ if (!cli->getCurrentDb())
+ {
+ println(tr("No working database is set.\n"
+ "Call %1 command to set working database.\n"
+ "Call %2 to see list of all databases.")
+ .arg(cmdName("use")).arg(cmdName("dblist")));
+
+ return;
+ }
+
+ Db* db = cli->getCurrentDb();
+ if (!db || !db->isOpen())
+ {
+ println(tr("Database is not open."));
+ return;
+ }
+
+ // Executor deletes itself later when called with lambda.
+ QueryExecutor *executor = new QueryExecutor(db, syntax.getArgument(STRING));
+ connect(executor, SIGNAL(executionFinished(SqlQueryPtr)), this, SIGNAL(execComplete()));
+ connect(executor, SIGNAL(executionFailed(int,QString)), this, SLOT(executionFailed(int,QString)));
+ connect(executor, SIGNAL(executionFailed(int,QString)), this, SIGNAL(execComplete()));
+
+ executor->exec([=](SqlQueryPtr results)
+ {
+ if (results->isError())
+ return; // should not happen, since results handler function is called only for successful executions
+
+ switch (CFG_CLI.Console.ResultsDisplayMode.get())
+ {
+ case CliResultsDisplay::FIXED:
+ printResultsFixed(executor, results);
+ break;
+ case CliResultsDisplay::COLUMNS:
+ printResultsColumns(executor, results);
+ break;
+ case CliResultsDisplay::ROW:
+ printResultsRowByRow(executor, results);
+ break;
+ default:
+ printResultsClassic(executor, results);
+ break;
+ }
+ });
+}
+
+QString CliCommandSql::shortHelp() const
+{
+ return tr("executes SQL query");
+}
+
+QString CliCommandSql::fullHelp() const
+{
+ return tr(
+ "This command is executed every time you enter SQL query in command prompt. "
+ "It executes the query on the current working database (see help for %1 for details). "
+ "There's no sense in executing this command explicitly. Instead just type the SQL query "
+ "in the command prompt, without any command prefixed."
+ ).arg(cmdName("use"));
+}
+
+bool CliCommandSql::isAsyncExecution() const
+{
+ return true;
+}
+
+void CliCommandSql::defineSyntax()
+{
+ syntax.setName("query");
+ syntax.addArgument(STRING, tr("sql", "CLI command syntax"));
+ syntax.setStrictArgumentCount(false);
+}
+
+void CliCommandSql::printResultsClassic(QueryExecutor* executor, SqlQueryPtr results)
+{
+ int metaColumns = executor->getMetaColumnCount();
+ int resultColumnCount = executor->getResultColumns().size();
+
+ // Columns
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns())
+ qOut << resCol->displayName << "|";
+
+ qOut << "\n";
+
+ // Data
+ SqlResultsRowPtr row;
+ QList<QVariant> values;
+ int i;
+ while (results->hasNext())
+ {
+ row = results->next();
+ i = 0;
+ values = row->valueList().mid(metaColumns);
+ foreach (QVariant value, values)
+ {
+ qOut << getValueString(value);
+ if ((i + 1) < resultColumnCount)
+ qOut << "|";
+
+ i++;
+ }
+
+ qOut << "\n";
+ }
+ qOut.flush();
+}
+
+void CliCommandSql::printResultsFixed(QueryExecutor* executor, SqlQueryPtr results)
+{
+ QList<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns();
+ int resultColumnsCount = resultColumns.size();
+ int metaColumns = executor->getMetaColumnCount();
+ int termCols = getCliColumns();
+ int baseColWidth = termCols / resultColumns.size() - 1;
+
+ if (resultColumnsCount == 0)
+ return;
+
+ if ((resultColumnsCount * 2 - 1) > termCols)
+ {
+ println(tr("Too many columns to display in %1 mode.").arg("FIXED"));
+ return;
+ }
+
+ int width;
+ QList<int> widths;
+ for (int i = 0; i < resultColumnsCount; i++)
+ {
+ width = baseColWidth;
+ if (i+1 == resultColumnsCount)
+ width += (termCols - resultColumnsCount * (baseColWidth + 1) + 1);
+
+ widths << width;
+ }
+
+ // Columns
+ QStringList columns;
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns())
+ columns << resCol->displayName;
+
+ printColumnHeader(widths, columns);
+
+ // Data
+ while (results->hasNext())
+ printColumnDataRow(widths, results->next(), metaColumns);
+
+ qOut.flush();
+}
+
+void CliCommandSql::printResultsColumns(QueryExecutor* executor, SqlQueryPtr results)
+{
+ // Check if we don't have more columns than we can display
+ QList<QueryExecutor::ResultColumnPtr> resultColumns = executor->getResultColumns();
+ int termCols = getCliColumns();
+ int resultColumnsCount = resultColumns.size();
+ QStringList headerNames;
+ if (resultColumnsCount == 0)
+ return;
+
+ // Every column requires at least 1 character width + column separators between them
+ if ((resultColumnsCount * 2 - 1) > termCols)
+ {
+ println(tr("Too many columns to display in %1 mode.").arg("COLUMNS"));
+ return;
+ }
+
+ // Preload data (we will calculate column widths basing on real values)
+ QList<SqlResultsRowPtr> allRows = results->getAll();
+ int metaColumns = executor->getMetaColumnCount();
+
+ // Get widths of each column in every data row, remember the longest ones
+ QList<SortedColumnWidth*> columnWidths;
+ SortedColumnWidth* colWidth = nullptr;
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, resultColumns)
+ {
+ colWidth = new SortedColumnWidth();
+ colWidth->setHeaderWidth(resCol->displayName.length());
+ columnWidths << colWidth;
+ headerNames << resCol->displayName;
+ }
+
+ int dataLength;
+ foreach (const SqlResultsRowPtr& row, allRows)
+ {
+ for (int i = 0; i < resultColumnsCount; i++)
+ {
+ dataLength = row->value(metaColumns + i).toString().length();
+ columnWidths[i]->setMinDataWidth(dataLength);
+ }
+ }
+
+ // Calculate width as it would be required to display entire rows
+ int totalWidth = 0;
+ foreach (colWidth, columnWidths)
+ totalWidth += colWidth->getWidth();
+
+ totalWidth += (resultColumnsCount - 1); // column separators
+
+ // Adjust column sizes to fit into terminal window
+ if (totalWidth < termCols)
+ {
+ // Expanding last column
+ int diff = termCols - totalWidth;
+ columnWidths.last()->incrWidth(diff);
+ }
+ else if (totalWidth > termCols)
+ {
+ // Shrinking columns
+ shrinkColumns(columnWidths, termCols, resultColumnsCount, totalWidth);
+ }
+
+ // Printing
+ QList<int> finalWidths;
+ foreach (colWidth, columnWidths)
+ finalWidths << colWidth->getWidth();
+
+ printColumnHeader(finalWidths, headerNames);
+
+ foreach (SqlResultsRowPtr row, allRows)
+ printColumnDataRow(finalWidths, row, metaColumns);
+
+ qOut.flush();
+}
+
+void CliCommandSql::printResultsRowByRow(QueryExecutor* executor, SqlQueryPtr results)
+{
+ // Columns
+ int metaColumns = executor->getMetaColumnCount();
+ int colWidth = 0;
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns())
+ {
+ if (resCol->displayName.length() > colWidth)
+ colWidth = resCol->displayName.length();
+ }
+
+ QStringList columns;
+ foreach (const QueryExecutor::ResultColumnPtr& resCol, executor->getResultColumns())
+ columns << pad(resCol->displayName, -colWidth, ' ');
+
+ // Data
+ static const QString rowCntTemplate = tr("Row %1");
+ int termWidth = getCliColumns();
+ QString rowCntString;
+ int i;
+ int rowCnt = 1;
+ SqlResultsRowPtr row;
+ while (results->hasNext())
+ {
+ row = results->next();
+ i = 0;
+ rowCntString = " " + rowCntTemplate.arg(rowCnt) + " ";
+ qOut << center(rowCntString, termWidth - 1, '-') << "\n";
+ foreach (QVariant value, row->valueList().mid(metaColumns))
+ {
+ qOut << columns[i] + ": " + getValueString(value) << "\n";
+ i++;
+ }
+ rowCnt++;
+ }
+ qOut.flush();
+}
+
+void CliCommandSql::shrinkColumns(QList<CliCommandSql::SortedColumnWidth*>& columnWidths, int termCols, int resultColumnsCount, int totalWidth)
+{
+ // This implements quite a smart shrinking algorithm:
+ // All columns are sorted by their current total width (data and header width)
+ // and then longest headers are shrinked first, then if headers are no longer a problem,
+ // but the data is - then longest data values are shrinked.
+ // If either the hader or the data value is huge (way more than fits into terminal),
+ // then such column is shrinked in one step to a reasonable width, so it can be later
+ // shrinked more precisely.
+ int maxSingleColumnWidth = (termCols - (resultColumnsCount - 1) * 2 );
+ bool shrinkData;
+ int previousTotalWidth = -1;
+ while (totalWidth > termCols && totalWidth != previousTotalWidth)
+ {
+ shrinkData = true;
+ previousTotalWidth = totalWidth;
+
+ // Sort columns by current widths
+ qSort(columnWidths);
+
+ // See if we can shrink headers only, or we already need to shrink the data
+ foreach (SortedColumnWidth* colWidth, columnWidths)
+ {
+ if (colWidth->isHeaderLonger())
+ {
+ shrinkData = false;
+ break;
+ }
+ }
+
+ // Do the shrinking
+ if (shrinkData)
+ {
+ for (int i = resultColumnsCount - 1; i >= 0; i--)
+ {
+ // If the data is way larger then the terminal, shrink it to reasonable length in one step.
+ // We also make sure that after data shrinking, the header didn't become longer than the data,
+ // cause at this moment, we were finished with headers and we enforce shrinking data
+ // and so do with headers.
+ if (columnWidths[i]->getDataWidth() > maxSingleColumnWidth)
+ {
+ totalWidth -= (columnWidths[i]->getDataWidth() - maxSingleColumnWidth);
+ columnWidths[i]->setDataWidth(maxSingleColumnWidth);
+ columnWidths[i]->setMaxHeaderWidth(maxSingleColumnWidth);
+ break;
+ }
+ else if (columnWidths[i]->getDataWidth() > 1) // just shrink it by 1
+ {
+ totalWidth -= 1;
+ columnWidths[i]->decrDataWidth();
+ columnWidths[i]->setMaxHeaderWidth(columnWidths[i]->getDataWidth());
+ break;
+ }
+ }
+ }
+ else // shrinking headers
+ {
+ for (int i = resultColumnsCount - 1; i >= 0; i--)
+ {
+ // We will shrink only the header that
+ if (!columnWidths[i]->isHeaderLonger())
+ continue;
+
+ // If the header is way larger then the terminal, shrink it to reasonable length in one step
+ if (columnWidths[i]->getHeaderWidth() > maxSingleColumnWidth)
+ {
+ totalWidth -= (columnWidths[i]->getHeaderWidth() - maxSingleColumnWidth);
+ columnWidths[i]->setHeaderWidth(maxSingleColumnWidth);
+ break;
+ }
+ else if (columnWidths[i]->getHeaderWidth() > 1) // otherwise just shrink it by 1
+ {
+ totalWidth -= 1;
+ columnWidths[i]->decrHeaderWidth();
+ break;
+ }
+ }
+ }
+ }
+
+ if (totalWidth == previousTotalWidth && totalWidth > termCols)
+ qWarning() << "The shrinking algorithm in printResultsColumns() failed, it could not shrink columns enough.";
+}
+
+void CliCommandSql::printColumnHeader(const QList<int>& widths, const QStringList& columns)
+{
+ QStringList line;
+ int i = 0;
+ foreach (const QString& col, columns)
+ {
+ line << pad(col.left(widths[i]), widths[i], ' ');
+ i++;
+ }
+
+ qOut << line.join("|");
+
+ line.clear();
+ QString hline("-");
+ for (i = 0; i < columns.count(); i++)
+ line << hline.repeated(widths[i]);
+
+ qOut << line.join("+");
+}
+
+void CliCommandSql::printColumnDataRow(const QList<int>& widths, const SqlResultsRowPtr& row, int rowIdOffset)
+{
+ int i = 0;
+ QStringList line;
+ foreach (const QVariant& value, row->valueList().mid(rowIdOffset))
+ {
+ line << pad(getValueString(value).left(widths[i]), widths[i], ' ');
+ i++;
+ }
+
+ qOut << line.join("|");
+}
+
+QString CliCommandSql::getValueString(const QVariant& value)
+{
+ if (value.isValid() && !value.isNull())
+ return value.toString();
+
+ return CFG_CLI.Console.NullValue.get();
+}
+
+void CliCommandSql::executionFailed(int code, const QString& msg)
+{
+ UNUSED(code);
+ qOut << tr("Query execution error: %1").arg(msg) << "\n\n";
+ qOut.flush();
+}
+
+CliCommandSql::SortedColumnWidth::SortedColumnWidth()
+{
+ dataWidth = 0;
+ headerWidth = 0;
+ width = 0;
+}
+
+bool CliCommandSql::SortedColumnWidth::operator<(const CliCommandSql::SortedColumnWidth& other)
+{
+ return width < other.width;
+}
+
+int CliCommandSql::SortedColumnWidth::getHeaderWidth() const
+{
+ return headerWidth;
+}
+
+void CliCommandSql::SortedColumnWidth::setHeaderWidth(int value)
+{
+ headerWidth = value;
+ updateWidth();
+}
+
+void CliCommandSql::SortedColumnWidth::setMaxHeaderWidth(int value)
+{
+ if (headerWidth > value)
+ {
+ headerWidth = value;
+ updateWidth();
+ }
+}
+
+void CliCommandSql::SortedColumnWidth::incrHeaderWidth(int value)
+{
+ headerWidth += value;
+ updateWidth();
+}
+
+void CliCommandSql::SortedColumnWidth::decrHeaderWidth(int value)
+{
+ headerWidth -= value;
+ updateWidth();
+}
+
+int CliCommandSql::SortedColumnWidth::getDataWidth() const
+{
+ return dataWidth;
+}
+
+void CliCommandSql::SortedColumnWidth::setDataWidth(int value)
+{
+ dataWidth = value;
+ updateWidth();
+}
+
+void CliCommandSql::SortedColumnWidth::setMinDataWidth(int value)
+{
+ if (dataWidth < value)
+ {
+ dataWidth = value;
+ updateWidth();
+ }
+}
+
+void CliCommandSql::SortedColumnWidth::incrDataWidth(int value)
+{
+ dataWidth += value;
+ updateWidth();
+}
+
+void CliCommandSql::SortedColumnWidth::decrDataWidth(int value)
+{
+ dataWidth -= value;
+ updateWidth();
+}
+
+void CliCommandSql::SortedColumnWidth::incrWidth(int value)
+{
+ width += value;
+ dataWidth = width;
+ headerWidth = width;
+}
+
+int CliCommandSql::SortedColumnWidth::getWidth() const
+{
+ return width;
+}
+
+bool CliCommandSql::SortedColumnWidth::isHeaderLonger() const
+{
+ return headerWidth > dataWidth;
+}
+
+void CliCommandSql::SortedColumnWidth::updateWidth()
+{
+ width = qMax(headerWidth, dataWidth);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h
new file mode 100644
index 0000000..5423a85
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h
@@ -0,0 +1,66 @@
+#ifndef CLICOMMANDSQL_H
+#define CLICOMMANDSQL_H
+
+#include "clicommand.h"
+#include "db/sqlquery.h"
+
+class QueryExecutor;
+
+class CliCommandSql : public CliCommand
+{
+ Q_OBJECT
+
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ bool isAsyncExecution() const;
+ void defineSyntax();
+
+ private:
+ class SortedColumnWidth
+ {
+ public:
+ SortedColumnWidth();
+
+ bool operator<(const SortedColumnWidth& other);
+
+ int getHeaderWidth() const;
+ void setHeaderWidth(int value);
+ void setMaxHeaderWidth(int value);
+ void incrHeaderWidth(int value = 1);
+ void decrHeaderWidth(int value = 1);
+
+ int getDataWidth() const;
+ void setDataWidth(int value);
+ void setMinDataWidth(int value);
+ void incrDataWidth(int value = 1);
+ void decrDataWidth(int value = 1);
+
+ void incrWidth(int value = 0);
+ int getWidth() const;
+ bool isHeaderLonger() const;
+
+ private:
+ void updateWidth();
+
+ int width;
+ int headerWidth;
+ int dataWidth;
+ };
+
+ void printResultsClassic(QueryExecutor *executor, SqlQueryPtr results);
+ void printResultsFixed(QueryExecutor *executor, SqlQueryPtr results);
+ void printResultsColumns(QueryExecutor *executor, SqlQueryPtr results);
+ void printResultsRowByRow(QueryExecutor *executor, SqlQueryPtr results);
+ void shrinkColumns(QList<SortedColumnWidth*>& columnWidths, int termCols, int resultColumnsCount, int totalWidth);
+ void printColumnHeader(const QList<int>& widths, const QStringList& columns);
+ void printColumnDataRow(const QList<int>& widths, const SqlResultsRowPtr& row, int rowIdOffset);
+
+ QString getValueString(const QVariant& value);
+
+ private slots:
+ void executionFailed(int code, const QString& msg);
+};
+
+#endif // CLICOMMANDSQL_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp
new file mode 100644
index 0000000..9a6b325
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp
@@ -0,0 +1,79 @@
+#include "clicommandtables.h"
+#include "cli.h"
+#include "schemaresolver.h"
+#include "services/dbmanager.h"
+#include "common/utils.h"
+
+void CliCommandTables::execute()
+{
+ Db* db = nullptr;
+ if (syntax.isArgumentSet(DB_NAME))
+ {
+ db = DBLIST->getByName(syntax.getArgument(DB_NAME));
+ if (!db)
+ {
+ println(tr("No such database: %1. Use .dblist to see list of known databases.").arg(syntax.getArgument(DB_NAME)));
+ return;
+ }
+ }
+ else if (cli->getCurrentDb())
+ {
+ db = cli->getCurrentDb();
+ }
+ else
+ {
+ println(tr("Cannot call %1 when no database is set to be current. Specify current database with %2 command or pass database name to %3.")
+ .arg(cmdName("tables")).arg(cmdName("use")).arg(cmdName("tables")));
+ return;
+ }
+
+ if (!db->isOpen())
+ {
+ println(tr("Database %1 is closed.").arg(db->getName()));
+ return;
+ }
+
+ println();
+
+ SchemaResolver resolver(db);
+ resolver.setIgnoreSystemObjects(!syntax.isOptionSet(SHOW_SYSTEM_TABLES));
+ QSet<QString> dbList;
+ dbList << "main" << "temp";
+ dbList += resolver.getDatabases();
+
+ int width = longest(dbList.toList()).length();
+ width = qMax(width, tr("Database").length());
+
+ println(pad(tr("Database"), width, ' ') + " " + tr("Table"));
+ println(pad("", width, '-') + "-------------------");
+
+ foreach (const QString& dbName, dbList)
+ {
+ foreach (const QString& table, resolver.getTables(dbName))
+ println(pad(dbName, width, ' ') + " " + table);
+ }
+
+ println();
+}
+
+QString CliCommandTables::shortHelp() const
+{
+ return tr("prints list of tables in the database");
+}
+
+QString CliCommandTables::fullHelp() const
+{
+ return tr(
+ "Prints list of tables in given <database> or in the current working database. "
+ "Note, that the <database> should be the name of the registered database (see %1). "
+ "The output list includes all tables from any other databases attached to the queried database.\n"
+ "When the -s option is given, then system tables are also listed."
+ ).arg(cmdName("use"));
+}
+
+void CliCommandTables::defineSyntax()
+{
+ syntax.setName("tables");
+ syntax.addArgument(DB_NAME, tr("database", "CLI command syntax"), false);
+ syntax.addOptionShort(SHOW_SYSTEM_TABLES, "s");
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h
new file mode 100644
index 0000000..68ae314
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h
@@ -0,0 +1,21 @@
+#ifndef CLICOMMANDTABLES_H
+#define CLICOMMANDTABLES_H
+
+#include "clicommand.h"
+
+class CliCommandTables : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum ArgIds
+ {
+ SHOW_SYSTEM_TABLES
+ };
+};
+
+#endif // CLICOMMANDTABLES_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp
new file mode 100644
index 0000000..cbd57c6
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp
@@ -0,0 +1,154 @@
+#include "clicommandtree.h"
+#include "cli.h"
+#include "common/sortedhash.h"
+#include "common/unused.h"
+
+const QString CliCommandTree::metaNodeNameTemplate = "<%1>";
+
+void CliCommandTree::execute()
+{
+ if (!cli->getCurrentDb())
+ {
+ println(tr("No current working database is selected. Use %1 to define one and then run %2.").arg(cmdName("use")).arg(cmdName("tree")));
+ return;
+ }
+
+ bool printColumns = syntax.isOptionSet(COLUMNS);
+ bool printSystemObjects = syntax.isOptionSet(SYSTEM_OBJECTS);
+
+ SchemaResolver resolver(cli->getCurrentDb());
+ resolver.setIgnoreSystemObjects(!printSystemObjects);
+
+ QStringList databases;
+ if (syntax.isArgumentSet(INTRNAL_DB))
+ {
+ databases << syntax.getArgument(INTRNAL_DB);
+ }
+ else
+ {
+ databases << "main" << "temp";
+ databases += resolver.getDatabases().toList();
+ }
+
+ AsciiTree tree;
+ tree.label = cli->getCurrentDb()->getName();
+ foreach (const QString& database, databases)
+ {
+ tree.childs << getDatabaseTree(database, resolver, printColumns);
+ }
+
+ println();
+ println(toAsciiTree(tree));
+ println();
+}
+
+AsciiTree CliCommandTree::getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns)
+{
+ QStringList tables = resolver.getTables(database);
+ QStringList views = resolver.getViews(database);
+
+ AsciiTree tree;
+ AsciiTree tablesTree;
+ AsciiTree viewsTree;
+
+ tablesTree.label = metaNodeNameTemplate.arg(tr("Tables"));
+ foreach (const QString& table, tables)
+ tablesTree.childs << getTableTree(database, table, resolver, printColumns);
+
+ viewsTree.label = metaNodeNameTemplate.arg(tr("Views"));
+ foreach (const QString& view, views)
+ viewsTree.childs << getViewTree(database, view, resolver);
+
+ tree.label = database;
+ tree.childs << tablesTree << viewsTree;
+ return tree;
+}
+
+AsciiTree CliCommandTree::getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns)
+{
+ QStringList columns;
+ if (printColumns)
+ columns = resolver.getTableColumns(database, table);
+
+ QStringList indexes = resolver.getIndexesForTable(database, table);
+ QStringList triggers = resolver.getTriggersForTable(database, table);
+
+ AsciiTree tree;
+ AsciiTree columnsTree;
+ AsciiTree indexesTree;
+ AsciiTree triggersTree;
+
+ if (printColumns)
+ {
+ columnsTree.label = metaNodeNameTemplate.arg(tr("Columns"));
+ foreach (const QString& column, columns)
+ columnsTree.childs << getTreeLeaf(column);
+ }
+
+ indexesTree.label = metaNodeNameTemplate.arg(tr("Indexes"));
+ foreach (const QString& index, indexes)
+ indexesTree.childs << getTreeLeaf(index);
+
+ triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers"));
+ foreach (const QString& trig, triggers)
+ triggersTree.childs << getTreeLeaf(trig);
+
+ if (printColumns)
+ tree.childs << columnsTree;
+
+ tree.label = table;
+ tree.childs << indexesTree;
+ tree.childs << triggersTree;
+
+ return tree;
+}
+
+AsciiTree CliCommandTree::getViewTree(const QString& database, const QString& view, SchemaResolver& resolver)
+{
+ QStringList triggers = resolver.getTriggersForView(database, view);
+
+ AsciiTree tree;
+ AsciiTree triggersTree;
+
+ triggersTree.label = metaNodeNameTemplate.arg(tr("Triggers"));
+ foreach (const QString& trig, triggers)
+ triggersTree.childs << getTreeLeaf(trig);
+
+ tree.label = view;
+ tree.childs << triggersTree;
+
+ return tree;
+}
+
+AsciiTree CliCommandTree::getTreeLeaf(const QString& column)
+{
+ AsciiTree tree;
+ tree.label = column;
+ return tree;
+}
+
+QString CliCommandTree::shortHelp() const
+{
+ return tr("prints all objects in the database as a tree");
+}
+
+QString CliCommandTree::fullHelp() const
+{
+ return tr(
+ "Prints all objects (tables, indexes, triggers and views) that are in the database as a tree. "
+ "The tree is very similar to the one that you can see in GUI client of the SQLiteStudio.\n"
+ "When -c option is given, then also columns will be listed under each table.\n"
+ "When -s option is given, then also system objects will be printed (sqlite_* tables, autoincrement indexes, etc).\n"
+ "The database argument is optional and if provided, then only given database will be printed. This is not a registered database name, "
+ "but instead it's an internal SQLite database name, like 'main', 'temp', or any attached database name. To print tree for other "
+ "registered database, call %1 first to switch the working database, and then use %2 command."
+ ).arg(cmdName("use")).arg(cmdName("tree"));
+}
+
+void CliCommandTree::defineSyntax()
+{
+ syntax.setName("tree");
+ syntax.addOptionShort(COLUMNS, "c");
+ syntax.addOptionShort(SYSTEM_OBJECTS, "s");
+ syntax.addArgument(INTRNAL_DB, "database", false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h
new file mode 100644
index 0000000..815d781
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h
@@ -0,0 +1,31 @@
+#ifndef CLICOMMANDTREE_H
+#define CLICOMMANDTREE_H
+
+#include "schemaresolver.h"
+#include "cliutils.h"
+#include "clicommand.h"
+
+class CliCommandTree : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+
+ private:
+ enum Opts
+ {
+ COLUMNS,
+ SYSTEM_OBJECTS
+ };
+
+ AsciiTree getDatabaseTree(const QString& database, SchemaResolver& resolver, bool printColumns);
+ AsciiTree getTableTree(const QString& database, const QString& table, SchemaResolver& resolver, bool printColumns);
+ AsciiTree getViewTree(const QString& database, const QString& view, SchemaResolver& resolver);
+ AsciiTree getTreeLeaf(const QString& column);
+
+ static const QString metaNodeNameTemplate;
+};
+
+#endif // CLICOMMANDTREE_H
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp
new file mode 100644
index 0000000..ef0f641
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp
@@ -0,0 +1,64 @@
+#include "clicommanduse.h"
+#include "cli.h"
+#include "services/config.h"
+#include "../cli_config.h"
+#include "services/dbmanager.h"
+
+void CliCommandUse::execute()
+{
+ if (syntax.isArgumentSet(DB_NAME))
+ {
+ if (!cli->getCurrentDb())
+ {
+ println(tr("No current database selected."));
+ return;
+ }
+ println(tr("Current database: %1").arg(cli->getCurrentDb()->getName()));
+ return;
+ }
+
+ Db* db = DBLIST->getByName(syntax.getArgument(DB_NAME));
+ if (!db)
+ {
+ println(tr("No such database: %1").arg(syntax.getArgument(DB_NAME)));
+ return;
+ }
+
+ cli->setCurrentDb(db);
+ CFG_CLI.Console.DefaultDatabase.set(db->getName());
+
+ println(tr("Current database: %1").arg(db->getName()));
+}
+
+QString CliCommandUse::shortHelp() const
+{
+ return tr("changes default working database");
+}
+
+QString CliCommandUse::fullHelp() const
+{
+ return tr(
+ "Changes current working database to <name>. If the <name> database is not registered in the application, "
+ "then the error message is printed and no change is made.\n"
+ "\n"
+ "What is current working database?\n"
+ "When you type a SQL query to be executed, it is executed on the default database, which is also known as "
+ "the current working database. Most of database-related commands can also work using default database, if no database was "
+ "provided in their arguments. The current database is always identified by command line prompt. "
+ "The default database is always defined (unless there is no database on the list at all).\n"
+ "\n"
+ "The default database can be selected in various ways:\n"
+ "- using %1 command,\n"
+ "- by passing database file name to the application startup parameters,\n"
+ "- by passing registered database name to the application startup parameters,\n"
+ "- by restoring previously selected default database from saved configuration,\n"
+ "- or when default database was not selected by any of the above, then first database from the registered databases list "
+ "becomes the default one."
+ ).arg(cmdName("use"));
+}
+
+void CliCommandUse::defineSyntax()
+{
+ syntax.setName("use");
+ syntax.addArgument(DB_NAME, tr("name", "CLI command syntax"), false);
+}
diff --git a/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h
new file mode 100644
index 0000000..9a8f280
--- /dev/null
+++ b/SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h
@@ -0,0 +1,15 @@
+#ifndef CLICOMMANDUSE_H
+#define CLICOMMANDUSE_H
+
+#include "clicommand.h"
+
+class CliCommandUse : public CliCommand
+{
+ public:
+ void execute();
+ QString shortHelp() const;
+ QString fullHelp() const;
+ void defineSyntax();
+};
+
+#endif // CLICOMMANDUSE_H