From 7167ce41b61d2ba2cdb526777a4233eb84a3b66a Mon Sep 17 00:00:00 2001 From: Unit 193 Date: Sat, 6 Dec 2014 17:33:25 -0500 Subject: Imported Upstream version 2.99.6 --- .../sqlitestudiocli/commands/clicommand.cpp | 334 ++++++++++++++ .../sqlitestudiocli/commands/clicommand.h | 97 ++++ .../sqlitestudiocli/commands/clicommandadd.cpp | 36 ++ .../sqlitestudiocli/commands/clicommandadd.h | 15 + .../sqlitestudiocli/commands/clicommandcd.cpp | 34 ++ .../sqlitestudiocli/commands/clicommandcd.h | 15 + .../sqlitestudiocli/commands/clicommandclose.cpp | 51 +++ .../sqlitestudiocli/commands/clicommandclose.h | 15 + .../sqlitestudiocli/commands/clicommanddblist.cpp | 86 ++++ .../sqlitestudiocli/commands/clicommanddblist.h | 15 + .../sqlitestudiocli/commands/clicommanddesc.cpp | 26 ++ .../sqlitestudiocli/commands/clicommanddesc.h | 16 + .../sqlitestudiocli/commands/clicommanddir.cpp | 50 ++ .../sqlitestudiocli/commands/clicommanddir.h | 15 + .../sqlitestudiocli/commands/clicommandexit.cpp | 26 ++ .../sqlitestudiocli/commands/clicommandexit.h | 15 + .../sqlitestudiocli/commands/clicommandfactory.cpp | 85 ++++ .../sqlitestudiocli/commands/clicommandfactory.h | 25 + .../sqlitestudiocli/commands/clicommandhelp.cpp | 86 ++++ .../sqlitestudiocli/commands/clicommandhelp.h | 19 + .../sqlitestudiocli/commands/clicommandhistory.cpp | 81 ++++ .../sqlitestudiocli/commands/clicommandhistory.h | 26 ++ .../sqlitestudiocli/commands/clicommandmode.cpp | 65 +++ .../sqlitestudiocli/commands/clicommandmode.h | 21 + .../commands/clicommandnullvalue.cpp | 32 ++ .../sqlitestudiocli/commands/clicommandnullvalue.h | 15 + .../sqlitestudiocli/commands/clicommandopen.cpp | 84 ++++ .../sqlitestudiocli/commands/clicommandopen.h | 15 + .../sqlitestudiocli/commands/clicommandpwd.cpp | 28 ++ .../sqlitestudiocli/commands/clicommandpwd.h | 15 + .../sqlitestudiocli/commands/clicommandremove.cpp | 51 +++ .../sqlitestudiocli/commands/clicommandremove.h | 15 + .../sqlitestudiocli/commands/clicommandsql.cpp | 506 +++++++++++++++++++++ .../sqlitestudiocli/commands/clicommandsql.h | 66 +++ .../sqlitestudiocli/commands/clicommandtables.cpp | 79 ++++ .../sqlitestudiocli/commands/clicommandtables.h | 21 + .../sqlitestudiocli/commands/clicommandtree.cpp | 154 +++++++ .../sqlitestudiocli/commands/clicommandtree.h | 31 ++ .../sqlitestudiocli/commands/clicommanduse.cpp | 64 +++ .../sqlitestudiocli/commands/clicommanduse.h | 15 + 40 files changed, 2445 insertions(+) create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommand.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommand.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandadd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandcd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandclose.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddblist.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddesc.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanddir.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandexit.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandfactory.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhelp.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandhistory.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandmode.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandnullvalue.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandopen.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandpwd.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandremove.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandsql.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtables.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommandtree.h create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.cpp create mode 100644 SQLiteStudio3/sqlitestudiocli/commands/clicommanduse.h (limited to 'SQLiteStudio3/sqlitestudiocli/commands') 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 + +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(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 +#include +#include +#include + +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 with given to list the databases list. " + "The 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 +#include + +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 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 is provided, it should be name of the database to close (as printed by %1 command). " + "The the 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 + +void CliCommandDbList::execute() +{ + if (!cli->getCurrentDb()) + { + println(tr("No current working database defined.")); + return; + } + + QString currentName = cli->getCurrentDb()->getName(); + + println(tr("Databases:")); + QList 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 + +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 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 + +QHash 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 CliCommandFactory::getAllCommands() +{ + QHash results; + QHashIterator 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 +#include + +class CliCommand; + +class CliCommandFactory +{ + public: + static void init(); + static CliCommand* getCommand(const QString& cmdName); + static QHash getAllCommands(); + static QStringList getCommandNames(); + + private: + typedef CliCommand* (*CliCommandCreatorFunc)(); + + static void registerCommand(CliCommandCreatorFunc func); + + static QHash 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 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 ." + ).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 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 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 +#include +#include + +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 " + " of the registered database to open, or it can be to the database file to open. " + "In the second case, the 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 + +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 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 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 +#include + +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 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 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 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 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 allRows = results->getAll(); + int metaColumns = executor->getMetaColumnCount(); + + // Get widths of each column in every data row, remember the longest ones + QList 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 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& 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& 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& 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& columnWidths, int termCols, int resultColumnsCount, int totalWidth); + void printColumnHeader(const QList& widths, const QStringList& columns); + void printColumnDataRow(const QList& 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 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 or in the current working database. " + "Note, that the 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 . If the 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 -- cgit v1.2.3