diff options
| author | 2014-12-06 17:33:25 -0500 | |
|---|---|---|
| committer | 2014-12-06 17:33:25 -0500 | |
| commit | 7167ce41b61d2ba2cdb526777a4233eb84a3b66a (patch) | |
| tree | a35c14143716e1f2c98f808c81f89426045a946f /SQLiteStudio3/sqlitestudiocli/commands | |
Imported Upstream version 2.99.6upstream/2.99.6
Diffstat (limited to 'SQLiteStudio3/sqlitestudiocli/commands')
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 |
