diff options
Diffstat (limited to 'SQLiteStudio3/coreSQLiteStudio/plugins')
50 files changed, 4022 insertions, 0 deletions
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp new file mode 100644 index 0000000..29397e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.cpp @@ -0,0 +1,58 @@ +#include "builtinplugin.h" +#include "services/pluginmanager.h" +#include <QMetaClassInfo> + +QString BuiltInPlugin::getName() const +{ + return metaObject()->className(); +} + +QString BuiltInPlugin::getTitle() const +{ + const char *title = getMetaInfo("title"); + if (!title) + return getName(); + + return title; +} + +QString BuiltInPlugin::getDescription() const +{ + return getMetaInfo("description"); +} + +int BuiltInPlugin::getVersion() const +{ + return QString(getMetaInfo("version")).toInt(); +} + +QString BuiltInPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +QString BuiltInPlugin::getAuthor() const +{ + return getMetaInfo("author"); +} + +bool BuiltInPlugin::init() +{ + return true; +} + +void BuiltInPlugin::deinit() +{ +} + +const char* BuiltInPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h new file mode 100644 index 0000000..ccb5c3d --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/builtinplugin.h @@ -0,0 +1,147 @@ +#ifndef BUILTINPLUGIN_H +#define BUILTINPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +/** + * @brief Helper class for implementing built-in plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined using Q_CLASSINFO. + * + * There are macros defined to help you with defining those details. You don't need to define + * Q_CLASSINFO and know all required keys. You can use following macros: + * <ul> + * <li>::SQLITESTUDIO_PLUGIN_TITLE</li> + * <li>::SQLITESTUDIO_PLUGIN_DESC</li> + * <li>::SQLITESTUDIO_PLUGIN_UI</li> + * <li>::SQLITESTUDIO_PLUGIN_VERSION</li> + * <li>::SQLITESTUDIO_PLUGIN_AUTHOR</li> + * </ul> + * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information, like this: + * @code + * class MyPlugin : GenericPlugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * SQLITESTUDIO_PLUGIN_TITLE("My plugin") + * SQLITESTUDIO_PLUGIN_DESC("Does nothing. It's an example plugin.") + * SQLITESTUDIO_PLUGIN_UI("formObjectName") + * SQLITESTUDIO_PLUGIN_VERSION(10000) + * SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + * }; + * @endcode + */class API_EXPORT BuiltInPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable form. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; +}; + +/** + * @def SQLITESTUDIO_PLUGIN_TITLE + * @brief Defines plugin title. + * + * This is a built-in plugin replacement for "title" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_TITLE(Title) Q_CLASSINFO("title", Title) + +/** + * @def SQLITESTUDIO_PLUGIN_DESC + * @brief Defines plugin description. + * + * This is a built-in plugin replacement for "description" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_DESC(Desc) Q_CLASSINFO("description", Desc) + +/** + * @def SQLITESTUDIO_PLUGIN_UI + * @brief Defines Qt Designer Form object name to be used in configuration dialog. + * + * This is a built-in plugin replacement for "ui" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_UI(FormName) Q_CLASSINFO("ui", FormName) + +/** + * @def SQLITESTUDIO_PLUGIN_VERSION + * @brief Defines plugin version. + * + * This is a built-in plugin replacement for "version" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_VERSION(Ver) Q_CLASSINFO("version", #Ver) + +/** + * @def SQLITESTUDIO_PLUGIN_AUTHOR + * @brief Defines an author of the plugin. + * + * This is a built-in plugin replacement for "author" key in external plugin's json metadata file. + */ +#define SQLITESTUDIO_PLUGIN_AUTHOR(Author) Q_CLASSINFO("author", Author) + +#endif // BUILTINPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h new file mode 100644 index 0000000..093c20e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/codeformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef CODEFORMATTERPLUGIN_H +#define CODEFORMATTERPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugin.h" + +class Db; + +class API_EXPORT CodeFormatterPlugin : virtual public Plugin +{ + public: + virtual QString getLanguage() const = 0; + virtual QString format(const QString& code, Db* contextDb) = 0; +}; + +#endif // CODEFORMATTERPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h new file mode 100644 index 0000000..d8a8f9b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/confignotifiableplugin.h @@ -0,0 +1,14 @@ +#ifndef CONFIGNOTIFIABLEPLUGIN_H +#define CONFIGNOTIFIABLEPLUGIN_H + +#include <QVariant> + +class CfgEntry; + +class ConfigNotifiablePlugin +{ + public: + virtual void configModified(CfgEntry* key, const QVariant& value) = 0; +}; + +#endif // CONFIGNOTIFIABLEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h new file mode 100644 index 0000000..2f2e62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbplugin.h @@ -0,0 +1,73 @@ +#ifndef DBPLUGIN_H +#define DBPLUGIN_H + +#include "db/db.h" +#include "db/dbpluginoption.h" +#include "plugins/plugin.h" + +/** + * @brief Interface for database plugins + * + * This is a specialization of Plugin interface, which all database plugins have to implement. + */ +class API_EXPORT DbPlugin : virtual public Plugin +{ + public: + /** + * @brief Creates database instance defined by the plugin. + * @param name Name for the database. + * @param path Path to the database file. + * @param options Options for the database passed while registering the database in the application. + * @param errorMessage If the result is null (on failure) and this pointer is not null, the error message will be stored in it. + * @return Database instance on success, or null pointer on failure. + * + * Options can contain for example password for an encrypted database, or other connection options. + */ + virtual Db* getInstance(const QString& name, const QString& path, const QHash<QString,QVariant> &options, QString* errorMessage = 0) = 0; + + /** + * @brief Provides label of what type is the database. + * @return Type label. + * + * The label is used for presenting to the user what kind of database this is. It's used on GUI + * to display database type in databases dialog. It's usually either "SQLite3" or "SQLite2", + * but it may be something else, like for example encrypted database might provide "Encrypted SQLite3", + * or something similar. + */ + virtual QString getLabel() const = 0; + + /** + * @brief Provides list of options configurable for this database plugin. + * @return List of options. + * + * DbDialog uses this to provide GUI interface, so user can configure connection options. + * For each element in the list DbDialog adds QLabel and the input widget for entering option value. + * Option values entered by user are later passed to getInstance() as second argument. + */ + virtual QList<DbPluginOption> getOptionsList() const = 0; + + /** + * @brief Generates suggestion for database name. + * @param baseValue Value enterd as file path in DbDialog. + * @return Generated name suggestion. + * + * This can be simply string representation of \p baseValue, + * but the plugin may add something characteristic for the plugin. + */ + virtual QString generateDbName(const QVariant& baseValue) = 0; + + /** + * @brief Tests if the given database support is provided by this plugin. + * @param db Database to test. + * @return true if the database is supported by this plugin, or false otherwise. + * + * Implementation of this method should check if given database object + * is of the same type, that those returned from getInstance(). + * + * This method is used by DbManager to find out which databases are supported by which plugins, + * so when some plugin is about to be unloaded, all its databases are closed properly first. + */ + virtual bool checkIfDbServedByPlugin(Db* db) const = 0; +}; + +#endif // DBPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp new file mode 100644 index 0000000..0f16098 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.cpp @@ -0,0 +1,47 @@ +#include "dbpluginsqlite3.h" +#include "db/dbsqlite3.h" +#include "common/unused.h" +#include <QFileInfo> + +Db* DbPluginSqlite3::getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage) +{ + UNUSED(errorMessage); + Db* db = new DbSqlite3(name, path, options); + + if (!db->openForProbing()) + { + delete db; + return nullptr; + } + + SqlQueryPtr results = db->exec("SELECT * FROM sqlite_master"); + if (results->isError()) + { + delete db; + return nullptr; + } + + db->closeQuiet(); + return db; +} + +QString DbPluginSqlite3::getLabel() const +{ + return "SQLite 3"; +} + +QList<DbPluginOption> DbPluginSqlite3::getOptionsList() const +{ + return QList<DbPluginOption>(); +} + +QString DbPluginSqlite3::generateDbName(const QVariant& baseValue) +{ + QFileInfo file(baseValue.toString()); + return file.baseName(); +} + +bool DbPluginSqlite3::checkIfDbServedByPlugin(Db* db) const +{ + return (db && dynamic_cast<DbSqlite3*>(db)); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h new file mode 100644 index 0000000..ee522d7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/dbpluginsqlite3.h @@ -0,0 +1,24 @@ +#ifndef DBPLUGINSQLITE3_H +#define DBPLUGINSQLITE3_H + +#include "dbplugin.h" +#include "builtinplugin.h" + +class DbPluginSqlite3 : public BuiltInPlugin, public DbPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQLite 3") + SQLITESTUDIO_PLUGIN_DESC("SQLite 3 databases support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + Db* getInstance(const QString& name, const QString& path, const QHash<QString, QVariant>& options, QString* errorMessage); + QString getLabel() const; + QList<DbPluginOption> getOptionsList() const; + QString generateDbName(const QVariant& baseValue); + bool checkIfDbServedByPlugin(Db* db) const; +}; + +#endif // DBPLUGINSQLITE3_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h new file mode 100644 index 0000000..06aded7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/exportplugin.h @@ -0,0 +1,372 @@ +#ifndef EXPORTPLUGIN_H +#define EXPORTPLUGIN_H + +#include "plugin.h" +#include "db/sqlquery.h" +#include "db/queryexecutor.h" +#include "services/exportmanager.h" +#include "parser/ast/sqlitecreatetable.h" +#include "parser/ast/sqlitecreateindex.h" +#include "parser/ast/sqlitecreatetrigger.h" +#include "parser/ast/sqlitecreateview.h" +#include "parser/ast/sqlitecreatevirtualtable.h" + +class CfgMain; + +/** + * @brief Provides support for particular export format. + * + * All export methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ExportPlugin : virtual public Plugin +{ + public: + /** + * @brief Provides name of the format that the plugin exports to. + * @return Format name that will be displayed on UI. + * + * Format must be a single word name. + */ + virtual QString getFormatName() const = 0; + + /** + * @brief Tells what standard exporting options should be displayed to the user on UI. + * @return OR-ed set of option enum values. + */ + virtual ExportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Tells which character encoding use by default in export dialog. + * @return Name of the encoding. + * + * If the plugin doesn't return ExportManager::CODEC in results from standardOptionsToEnable(), then result + * of this function is ignored and it can return null string. + */ + virtual QString getDefaultEncoding() const = 0; + + /** + * @brief Provides set of modes supported by this export plugin. + * @return OR-ed set of supported modes. + * + * Some export plugins might not support some of exporting modes. For example CSV export plugin + * will not support DATABASE exporting, because CSV cannot represent schema of the database. + * + * If a plugin doesn't return some mode in this method, then that plugin will be excluded + * from list of available formats to export, when user requests to export in this mode. + */ + virtual ExportManager::ExportModes getSupportedModes() const = 0; + + /** + * @brief Provides set of flags for additional information that needs to be provided for this plugin. + * @return OR-ed set of flags. + * + * Some plugins might need to know what is a total number of rows that are expected to be + * exported for each table or query results. Other plugins might want to know + * what is the maximum number of characters/bytes in each exported table column, + * so they can calculate column widths when drawing them in the exported files, documents, etc. + * + * Those additional information are not provided by default, because they are gathered with extra queries + * to the database and for huge tables it might cause the table to be exported much longer, even if + * those information wasn't required by some plugin. + * + * See ExportManager::ExportProviderFlags for list of possible flags and what they mean. + */ + virtual ExportManager::ExportProviderFlags getProviderFlags() const = 0; + + /** + * @brief Provides config object that holds configuration for exporting. + * @return Config object, or null if the exporting with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of exporting in given mode. + * @param mode Mode for which the form is requested for. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If exporting with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getExportConfigFormName() const = 0; + + /** + * @brief Tells plugin what is going to be the next export mode. + * @param mode Mode that the next export is going to be performed for. + * + * Plugin should remember this and use later when some logic is depended on what the mode is. + * For example getConfigFormName() (as well as getConfig()) might return different config forms + * for different modes. + */ + virtual void setExportMode(ExportManager::ExportMode mode) = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling EXPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + */ + virtual void validateOptions() = 0; + + /** + * @brief Provides usual file name extension used with this format. + * @return File name extension (like ".csv"). + * + * This extension will be automatically appended to file name when user picked file name + * with file dialog, but the file extension part in the selected file was ommited. + */ + virtual QString defaultFileExtension() const = 0; + + /** + * @brief Mime type for when exporting binary format to clipboard. + * @return Mime type, like "image/png". + * + * Value returned from this method is used to set mime type when the exporting is done into the system clipboard. + * The clipboard needs mime type to identify what kind of data is in it. + * + * See details http://qt-project.org/doc/qt-5/qmimedata.html#setData + * + * If the plugin exports just a string, then this method can return QString::null and SqliteStudio will assume + * that the data is of "text/plain" type. + */ + virtual QString getMimeType() const = 0; + + /** + * @brief Tells if the data being exported is a binary or text. + * @return true for binary data, false for textual data. + * + * This is used by the SQLiteStudio to configure output device properly. For example CSV format is textual, + * but PNG is considered binary. + */ + virtual bool isBinaryData() const = 0; + + /** + * @brief Provides common state values before the export process begins. + * @param db Database that the export will be performed on. + * @param output Output device to write exporting data to. + * @param config Common exporting configuration, like file name, codec, etc. + * @return true for success, or false in case of a fatal error. + * + * This is called exactly once before every export process (that is once per each export called by user). + * Use it to remember database, output device, config for further method calls, or write a file header. + * This method will be followed by any of *export*() methods from this interface. + * + * There's a convenient class GenericExportPlugin, which you can extend instead of ExportPlugin. If you use + * GenericExportPlugin for a base class of exprt plugin, then this method is already implemented there + * and it stores all these parameters in protected class members so you can use them in other methods. + */ + virtual bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) = 0; + + /** + * @brief Does initial entry for exported query results. + * @param query Query that was executed to get the results. + * @param columns Columns returned from the query. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + * + * It's called just before actual data entries are exported (with exportQueryResultsRow()). + * It's called exactly once for single query results export. + */ + virtual bool beforeExportQueryResults(const QString& query, QList<QueryExecutor::ResultColumnPtr>& columns, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param row Single data row. + * @return true for success, or false in case of a fatal error. + * + * It's called for each data row returned from the query. + */ + virtual bool exportQueryResultsRow(SqlResultsRowPtr row) = 0; + + /** + * @brief Does final entry for exported query results. + * @return true for success, or false in case of a fatal error. + * + * It's called once after all data from the query was exported. + */ + virtual bool afterExportQueryResults() = 0; + + /** + * @brief Prepares for exporting tables from database. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + */ + virtual bool beforeExportTables() = 0; + + /** + * @brief Does initial entry for exported table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, + const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does initial entry for exported virtual table. + * @param database "Attach" name of the database that the table belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the table to export. + * @param columnNames Name of columns in the table, in order they will appear in the rows passed to exportTableRow(). + * @param ddl The DDL of the table. + * @param createTable Table DDL parsed into an object. + * @param providedData All data entries requested by the plugin in the return value of getProviderFlags(). + * @return true for success, or false in case of a fatal error. + */ + virtual bool exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, + SqliteCreateVirtualTablePtr createTable, const QHash<ExportManager::ExportProviderFlag,QVariant> providedData) = 0; + + /** + * @brief Does export entry for a single row of data. + * @param data Single data row. + * @return true for success, or false in case of a fatal error. + * + * This method will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool exportTableRow(SqlResultsRowPtr data) = 0; + + /** + * @brief Does final entry for exported table, after its data was exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTable() = 0; + + /** + * @brief Does final entries after all tables have been exported. + * @return true for success, or false in case of a fatal error. + * + * This is called only for database export. For single table export only exportTable() is called. + * After table exporting also an afterExport() is called, so you can use that for any postprocessing. + */ + virtual bool afterExportTables() = 0; + + /** + * @brief Does initial entry for the entire database export. + * @param database Database name (as listed in database list). + * @return true for success, or false in case of a fatal error. + * + * It's called just once, before each database object gets exported. + * This method will be followed by calls to (in this order): beforeExportTables(), exportTable(), exportVirtualTable(), exportTableRow(), afterExportTable(), + * afterExportTables(), beforeExportIndexes(), exportIndex(), afterExportIndexes(), beforeExportTriggers(), exportTrigger(), afterExportTriggers(), + * beforeExportViews(), exportView(), afterExportViews() and afterExportDatabase(). + * Note, that exportTableRow() will be called only if StandardExportConfig::exportData in initBeforeExport() was true. + */ + virtual bool beforeExportDatabase(const QString& database) = 0; + + /** + * @brief Prepares for exporting indexes from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportIndexes() = 0; + + /** + * @brief Does entire export entry for an index. + * @param database "Attach" name of the database that the index belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the index to export. + * @param ddl The DDL of the index. + * @param createIndex Index DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for index export. + */ + virtual bool exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex) = 0; + + /** + * @brief Does final entries after all indexes have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportIndexes() = 0; + + /** + * @brief Prepares for exporting triggers from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportTriggers() = 0; + + /** + * @brief Does entire export entry for an trigger. + * @param database "Attach" name of the database that the trigger belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to export. + * @param ddl The DDL of the trigger. + * @param createTrigger Trigger DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for trigger export. + */ + virtual bool exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger) = 0; + + /** + * @brief Does final entries after all triggers have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportTriggers() = 0; + + /** + * @brief Prepares for exporting views from database. + * @return true for success, or false in case of a fatal error. + */ + virtual bool beforeExportViews() = 0; + + /** + * @brief Does entire export entry for an view. + * @param database "Attach" name of the database that the view belongs to. Can be "main", "temp", or any attach name. + * @param table Name of the trigger to view. + * @param ddl The DDL of the view. + * @param createView View DDL parsed into an object. + * @return true for success, or false in case of a fatal error. + * + * This is the only method called for view export. + */ + virtual bool exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view) = 0; + + /** + * @brief Does final entries after all views have been exported. + * @return true for success, or false in case of a fatal error. + */ + virtual bool afterExportViews() = 0; + + /** + * @brief Does final entry for the entire database export. + * @return true for success, or false in case of a fatal error. + * + * It's called just once, after all database object get exported. + */ + virtual bool afterExportDatabase() = 0; + + /** + * @brief Does final entry for any export process. + * @return true for success, or false in case of a fatal error. + * + * This is similar to afterExportDatabase() when the export mode is database, + * but this is called at the end for any export mode, not only for database export. + * + * Use it to write a footer, or anything like that. + */ + virtual bool afterExport() = 0; + + /** + * @brief Called after every export, even failed one. + * + * Implementation of this method should cleanup any resources used during each single export process. + * This method is guaranteed to be executed, no matter if export was successful or not. + */ + virtual void cleanupAfterExport() = 0; +}; + +#endif // EXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h new file mode 100644 index 0000000..b50834a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/generalpurposeplugin.h @@ -0,0 +1,20 @@ +#ifndef GENERALPURPOSEPLUGIN_H
+#define GENERALPURPOSEPLUGIN_H
+
+#include "plugin.h"
+
+/**
+ * @brief The general purpose plugin interface.
+ *
+ * General purpose plugins are not designated for some specific function.
+ * They rely on init() and deinit() implementations to add some menubar entries,
+ * or toolbar entries (or anything else), so user can interact with the plugin.
+ *
+ * @see Plugin
+ * @see GenericPlugin
+ */
+class API_EXPORT GeneralPurposePlugin : virtual public Plugin
+{
+};
+
+#endif // GENERALPURPOSEPLUGIN_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp new file mode 100644 index 0000000..d191ae4 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.cpp @@ -0,0 +1,160 @@ +#include "genericexportplugin.h" +#include "common/utils.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include "config_builder.h" +#include <QTextCodec> + +bool GenericExportPlugin::initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config) +{ + this->db = db; + this->output = output; + this->config = &config; + + if (standardOptionsToEnable().testFlag(ExportManager::CODEC)) + { + codec = codecForName(this->config->codec); + if (!codec) + { + codec = defaultCodec(); + notifyWarn(tr("Could not initialize text codec for exporting. Using default codec: %1").arg(QString::fromLatin1(codec->name()))); + } + } + + return beforeExport(); +} + +ExportManager::ExportModes GenericExportPlugin::getSupportedModes() const +{ + return ExportManager::FILE|ExportManager::CLIPBOARD|ExportManager::DATABASE|ExportManager::TABLE|ExportManager::QUERY_RESULTS; +} + +ExportManager::ExportProviderFlags GenericExportPlugin::getProviderFlags() const +{ + return ExportManager::NONE; +} + +QString GenericExportPlugin::getExportConfigFormName() const +{ + return QString(); +} + +CfgMain* GenericExportPlugin::getConfig() +{ + return nullptr; +} + +QString GenericExportPlugin::getConfigFormName(ExportManager::ExportMode mode) const +{ + UNUSED(mode); + return QString::null; +} + +QString GenericExportPlugin::getMimeType() const +{ + return QString::null; +} + +QString GenericExportPlugin::getDefaultEncoding() const +{ + return QString(); +} + +bool GenericExportPlugin::isBinaryData() const +{ + return false; +} + +void GenericExportPlugin::setExportMode(ExportManager::ExportMode mode) +{ + this->exportMode = mode; +} + +bool GenericExportPlugin::afterExportQueryResults() +{ + return true; +} + +bool GenericExportPlugin::afterExportTable() +{ + return true; +} + +bool GenericExportPlugin::initBeforeExport() +{ + return true; +} + +void GenericExportPlugin::write(const QString& str) +{ + output->write(codec->fromUnicode(str)); +} + +void GenericExportPlugin::writeln(const QString& str) +{ + write(str + "\n"); +} + +bool GenericExportPlugin::isTableExport() const +{ + return exportMode == ExportManager::TABLE; +} + +bool GenericExportPlugin::beforeExportTables() +{ + return true; +} + +bool GenericExportPlugin::afterExportTables() +{ + return true; +} + +bool GenericExportPlugin::beforeExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::afterExportIndexes() +{ + return true; +} + +bool GenericExportPlugin::beforeExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::afterExportTriggers() +{ + return true; +} + +bool GenericExportPlugin::beforeExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportViews() +{ + return true; +} + +bool GenericExportPlugin::afterExportDatabase() +{ + return true; +} + +bool GenericExportPlugin::afterExport() +{ + return true; +} + +void GenericExportPlugin::cleanupAfterExport() +{ +} + +bool GenericExportPlugin::beforeExport() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h new file mode 100644 index 0000000..1719baa --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericexportplugin.h @@ -0,0 +1,56 @@ +#ifndef GENERICEXPORTPLUGIN_H +#define GENERICEXPORTPLUGIN_H + +#include "exportplugin.h" +#include "genericplugin.h" + +class API_EXPORT GenericExportPlugin : virtual public GenericPlugin, public ExportPlugin +{ + public: + bool initBeforeExport(Db* db, QIODevice* output, const ExportManager::StandardExportConfig& config); + ExportManager::ExportModes getSupportedModes() const; + ExportManager::ExportProviderFlags getProviderFlags() const; + QString getExportConfigFormName() const; + CfgMain* getConfig(); + QString getConfigFormName(ExportManager::ExportMode exportMode) const; + QString getMimeType() const; + QString getDefaultEncoding() const; + bool isBinaryData() const; + void setExportMode(ExportManager::ExportMode exportMode); + bool afterExportQueryResults(); + bool afterExportTable(); + bool beforeExportTables(); + bool afterExportTables(); + bool beforeExportIndexes(); + bool afterExportIndexes(); + bool beforeExportTriggers(); + bool afterExportTriggers(); + bool beforeExportViews(); + bool afterExportViews(); + bool afterExportDatabase(); + bool afterExport(); + void cleanupAfterExport(); + + /** + * @brief Does the initial entry in the export. + * @return true for success, or false in case of a fatal error. + * + * Use it to write the header, or something like that. Default implementation does nothing. + * This is called from initBeforeExport(), after exportMode and other settings are already prepared. + */ + virtual bool beforeExport(); + + protected: + virtual bool initBeforeExport(); + void write(const QString& str); + void writeln(const QString& str); + bool isTableExport() const; + + Db* db = nullptr; + QIODevice* output = nullptr; + const ExportManager::StandardExportConfig* config = nullptr; + QTextCodec* codec = nullptr; + ExportManager::ExportMode exportMode = ExportManager::UNDEFINED; +}; + +#endif // GENERICEXPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp new file mode 100644 index 0000000..899691c --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.cpp @@ -0,0 +1,67 @@ +#include "genericplugin.h" +#include "services/pluginmanager.h" +#include <QMetaClassInfo> + +QString GenericPlugin::getName() const +{ + return metaData["name"].toString(); +} + +QString GenericPlugin::getTitle() const +{ + if (!metaData["title"].isValid()) + return getName(); + + return metaData["title"].toString(); +} + +CfgMain* GenericPlugin::getMainUiConfig() +{ + return nullptr; +} + +QString GenericPlugin::getDescription() const +{ + return metaData["description"].toString(); +} + +int GenericPlugin::getVersion() const +{ + return metaData["version"].toInt(); +} + +QString GenericPlugin::getPrintableVersion() const +{ + return PLUGINS->toPrintableVersion(getVersion()); +} + +bool GenericPlugin::init() +{ + return true; +} + +void GenericPlugin::deinit() +{ +} + +void GenericPlugin::loadMetaData(const QJsonObject& metaData) +{ + this->metaData = PLUGINS->readMetaData(metaData); +} + +const char* GenericPlugin::getMetaInfo(const QString& key) const +{ + for (int i = 0; i < metaObject()->classInfoCount(); i++) + { + if (key != metaObject()->classInfo(i).name()) + continue; + + return metaObject()->classInfo(i).value(); + } + return nullptr; +} + +QString GenericPlugin::getAuthor() const +{ + return metaData["author"].toString(); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h new file mode 100644 index 0000000..ce008e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/genericplugin.h @@ -0,0 +1,126 @@ +#ifndef GENERICPLUGIN_H +#define GENERICPLUGIN_H + +#include "plugin.h" +#include <QObject> +#include <QtPlugin> +#include <QHash> +#include <QVariant> + +/** @file */ + +/** + * @brief Helper class for implementing plugins + * + * This class can be inherited, so most of the abstract methods from Plugin interface get implemented. + * All details (description, name, title, author, ...) are defined in separate json file. + * + * Most of plugin implementations will use this class as a base, because it simplifies process + * of plugin development. Using this class you don't have to implement any of virtual methods + * from Plugin interface. It's enough to define meta information in the json file, like this: + * @code + * { + * "type": "ScriptingPlugin", + * "title": "My plugin", + * "description": "Does nothing. It's an example plugin.", + * "version": 10000 + * "author": "sqlitestudio.pl" + * }; + * @endcode + * + * and then just declare the class as SQLiteStudio plugin, pointing the json file you just created: + * @code + * class MyPlugin : public GenericPlugin, public ScriptingPlugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN("myplugin.json") + * + * // rest of the class + * }; + * @endcode + */ +class API_EXPORT GenericPlugin : public QObject, public virtual Plugin +{ + Q_OBJECT + Q_INTERFACES(Plugin) + + public: + /** + * @brief Provides plugin internal name. + * @return Plugin class name. + */ + QString getName() const; + + /** + * @brief Provides plugin title. + * @return Title defined in plugin's metadata file with key "title" or (if not defined) the same value as getName(). + */ + QString getTitle() const; + + /** + * @brief Provides configuration object to use in ConfigDialog. + * @return This implementation always returns null. + */ + CfgMain* getMainUiConfig(); + + /** + * @brief Provides plugin description. + * @return Description as defined in plugin's metadata file with key "description", or null QString if not defined. + */ + QString getDescription() const; + + /** + * @brief Provides plugin numeric version. + * @return Version number as defined in plugin's metadata file with key "version", or 0 if not defined. + */ + int getVersion() const; + + /** + * @brief Converts plugin version to human readable format. + * @return Version in format X.Y.Z. + */ + QString getPrintableVersion() const; + + /** + * @brief Provides an author name. + * @return Author name as defined with in plugin's metadata file with key "author", or null QString if not defined. + */ + QString getAuthor() const; + + /** + * @brief Does nothing. + * @return Always true. + * + * This is a default (empty) implementation of init() for plugins. + */ + bool init(); + + /** + * @brief Does nothing. + * + * This is a default (empty) implementation of init() for plugins. + */ + void deinit(); + + /** + * @brief Loads metadata from given Json object. + * @param The metadata from json file. + * + * This is called by PluginManager. + */ + void loadMetaData(const QJsonObject& metaData); + + private: + /** + * @brief Extracts class meta information with given key. + * @param key Key to extract. + * @return Value of the meta information, or null if there's no information with given key. + * + * This is a helper method which queries Qt's meta object subsystem for class meta information defined with Q_CLASSINFO. + */ + const char* getMetaInfo(const QString& key) const; + + QHash<QString,QVariant> metaData; +}; + +#endif // GENERICPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h new file mode 100644 index 0000000..ff520cd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/importplugin.h @@ -0,0 +1,132 @@ +#ifndef IMPORTPLUGIN_H +#define IMPORTPLUGIN_H + +#include "plugin.h" +#include "services/importmanager.h" + +class QIODevice; +class CfgMain; + +/** + * @brief Provides support for particular import format. + * + * All import methods in this class should report any warnings, error messages, etc through the NotifyManager, + * that is by using notifyError() and its family methods. + */ +class ImportPlugin : virtual public Plugin +{ + public: + /** + * @brief Pair of column name and its data type. + */ + typedef QPair<QString,QString> ColumnDefinition; + + /** + * @brief Used to show this plugin in the combo of data source types in the import dialog. + * @return String representing this plugin in the import dialog. + */ + virtual QString getDataSourceTypeName() const = 0; + + /** + * @brief Tells which standard import options should be available on to the user. + * @return OR-ed set of standard option enums. + */ + virtual ImportManager::StandardConfigFlags standardOptionsToEnable() const = 0; + + /** + * @brief Provides file name filter for file dialog. + * @return Filter compliant with QFileDialog documentation. + * + * If your plugin does not return ImportManager::FILE_NAME, this method can simply return QString::null. + * If your plugin does use input file name, then this method can (but don't have to) return file name filter + * to match expected files when user browses for the input file. + * + * The filter value (if not null) is passed directly to the QFileDialog. + */ + virtual QString getFileFilter() const = 0; + + /** + * @brief Called before each import that user makes. + * @param config Standard options configured by user in the import dialog. + * @return true if everything looks fine from plugin's perspective, or false otherwise. + * + * In case there were some problems at this step, plugin should return false, but before that it should tell what was wrong using NotifyManager's global shortcut + * method: notifyError(). + */ + virtual bool beforeImport(const ImportManager::StandardImportConfig& config) = 0; + + /** + * @brief Called after import process has been finished (successfully or not). + * + * Implement this method to clean up any resources that the plugin has initialized before. + */ + virtual void afterImport() = 0; + + /** + * @brief Provides list of columns (with their datatypes) for the data to be imported. + * @return List of columns, each column consists of column name and its data type definition. + * + * The ColumnDefinition is actually a QPair of two QString types. First in the pair is column name, second is column's data type, + * as a string representation. + * + * Let's say your plugin wants to import data that fits into 2 columns, first of <tt>INTEGER</tt> type and the second of <tt>VARCHAR(0, 5)</tt> type. + * You would write it like this: + * @code + * QList<ColumnDefinition> list; + * list << ColumnDefinition("column1", "INTEGER"); + * list << ColumnDefinition("column2", "VARCHAR (0, 5)"); + * return list; + * @endcode + */ + virtual QList<ColumnDefinition> getColumns() const = 0; + + /** + * @brief Provides next set of data from the data source. + * @return List of values, where number of elements must be equal to number of columns returned from getColumns(). + * + * This is essential import plugin method. It provides the data. + * This method simply provides next row of the data for a table. + * It will be called again and again, until it returns empty list, which will be interpreted as the end of data to import. + */ + virtual QList<QVariant> next() = 0; + + /** + * @brief Provides config object that holds configuration for importing. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of import dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If importing with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getImportConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true when validation was successful, or false if any error occured. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling IMPORT_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call IMPORT_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + */ + virtual bool validateOptions() = 0; +}; + +#endif // IMPORTPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h new file mode 100644 index 0000000..183adf7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugin.h @@ -0,0 +1,182 @@ +#ifndef PLUGIN_H +#define PLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include <QString> +#include <QHash> +#include <QtPlugin> + +class PluginType; +class CfgMain; + +/** @file */ + +/** + * @brief General plugin interface. + * + * This is the top-most generic interface for SQLiteStudio plugins. + * It's based in Qt's plugins framework. Every SQLiteStudio plugin must + * implement this (or its descendant) interface. + * + * SQLiteStudio plugin is basicly class implementing this interface, + * compiled as shared library (*.dll, *.so, *.dylib). + * + * Apart from implementing Plugin interface, the plugin class must also declare ::SQLITESTUDIO_PLUGIN macro, like this: + * @code + * class MyPlugin : Plugin + * { + * Q_OBJECT + * + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * }; + * @endcode + * + * Full tutorial for writting plugins is at: http://wiki.sqlitestudio.pl/index.php/Writting_plugins + * + * SQLiteStudio looks for plugins in following directories: + * <ul> + * <li><tt>{current_executable_dir}/plugins</tt> - a "plugins" subdirectory of the directory where application binary is placed,</li> + * <li><tt>{configuration_dir}/plugins</tt> - a "plugins" subdirectory of configuration directory detected and defined in Config,</li> + * <li><tt>{env_var:SQLITESTUDIO_PLUGINS}</tt> - environment variable with name "SQLITESTUDIO_PLUGINS",</li> + * <li><tt>{compile_time:PLUGINS_DIR}</tt> - compile time defined parameter's value of parameter with the name "PLUGINS_DIR".</li> + * </ul> + */ +class API_EXPORT Plugin +{ + public: + /** + * @brief Virtual destructor to make sure all plugins are destroyed correctly. + */ + virtual ~Plugin() {} + + /** + * @brief Gets name of the plugin. + * @return Name of the plugin. + * + * The name of the plugin is a kind of primary key for plugins. It has to be unique across all loaded plugins. + * An attempt to load two plugins with the same name will result in failed load of the second plugin. + * + * The name is a kind of internal plugin's name. It's designated for presenting to the user + * - for that purpose there is a getTitle(). + * + * It's a good practice to keep it as single word. Providing plugin's class name can be a good idea. + * + * BUG: Currently this implementation of this method has to always return the name of the plugin's main implementation class + * (like DbSqlite2), otherwise SQLiteStudio will either unable to load it, or dependencies to this plugin will fail. + * This has to do with PluginManagerImpl relying on "className" entry returned from QPluginLoader's metadata. + */ + virtual QString getName() const = 0; + + /** + * @brief Gets title for the plugin. + * @return Plugin title. + * + * This is plugin's name to be presented to the user. It can be multiple words name. It should be localized (translatable) text. + * It's used solely for presenting plugin to the user, nothing more. + */ + virtual QString getTitle() const = 0; + + /** + * @brief Provides name of the plugin's author. + * @return Author name. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getAuthor() const = 0; + + /** + * @brief Provides some details on what does the plugin. + * @return Plugin description. + * + * This is displayed in ConfigDialog when user clicks on Details button of the plugin. + */ + virtual QString getDescription() const = 0; + + /** + * @brief Provides plugin version number. + * @return Version number. + * + * Version number format can be picked by plugin developer, but it is recommended + * to use XXYYZZ, where XX is major version, YY is minor version and ZZ is patch version. + * Of course the XX can be single X if major version is less then 10. + * + * This would result in versions like: 10000 (for version 1.0.0), or 10102 (for version 1.1.2), + * or 123200 (for version 12.32.0). + * + * This is of course just a suggestion, you don't have to stick to it. Just keep in mind, + * that this number is used by SQLiteStudio to compare plugin versions. If there's a plugin with higher version, + * SQLiteStudio will propose to update it. + * + * The suggested format is also easier to convert to printable (string) version later in getPrintableVersion(). + */ + virtual int getVersion() const = 0; + + /** + * @brief Provides formatted version string. + * @return Version string. + * + * It provides string that represents version returned from getVersion() in a human-readable form. + * It's a good practice to return versions like "1.3.2", or "1.5", as they are easy to read. + * + * This version string is presented to the user. + */ + virtual QString getPrintableVersion() const = 0; + + /** + * @brief Initializes plugin just after it was loaded. + * @return true on success, or false otherwise. + * + * This is called as a first, just after plugin was loaded. If it returns false, + * then plugin loading is considered to be failed and gets unloaded. + * + * If this method returns false, then deinit() is not called. + */ + virtual bool init() = 0; + + /** + * @brief Deinitializes plugin that is about to be unloaded. + * + * This is called just before plugin is unloaded. It's called only when plugin was loaded + * successfully. It's NOT called when init() returned false. + */ + virtual void deinit() = 0; +}; + +/** + * @def SqliteStudioPluginInterface + * @brief SQLiteStudio plugin interface ID. + * + * This is an ID string for Qt's plugins framework. It's used by ::SQLITESTUDIO_PLUGIN macro. + * No need to use it directly. + */ +#define SqliteStudioPluginInterface "pl.sqlitestudio.Plugin/1.0" + +/** + * @def SQLITESTUDIO_PLUGIN + * @brief Defines class as a SQLiteStudio plugin + * + * Every class implementing SQLiteStudio plugin must have this declaration, + * otherwise SQLiteStudio won't be able to load the plugin. + * + * It has to be placed in class declaration: + * @code + * class MyPlugin : public QObject, public Plugin + * { + * Q_OBJECT + * SQLITESTUDIO_PLUGIN + * + * public: + * // ... + * } + * @endcode + */ +#define SQLITESTUDIO_PLUGIN(file)\ + Q_PLUGIN_METADATA(IID SqliteStudioPluginInterface FILE file) \ + Q_INTERFACES(Plugin) + +Q_DECLARE_INTERFACE(Plugin, SqliteStudioPluginInterface) + +#endif // PLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp new file mode 100644 index 0000000..39988bd --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.cpp @@ -0,0 +1,45 @@ +#include "pluginsymbolresolver.h" +#include <QCoreApplication> +#include <QDir> + +PluginSymbolResolver::PluginSymbolResolver() +{ +} + +void PluginSymbolResolver::addFileNameMask(const QString &mask) +{ + nameFilters << mask; +} + +void PluginSymbolResolver::addLookupSubFolder(const QString &name) +{ + subFolders << name; +} + +bool PluginSymbolResolver::load() +{ + QStringList paths = qApp->libraryPaths(); + foreach (QString path, paths) + foreach (QString subFolder, subFolders) + paths << path + "/" + subFolder; + + foreach (QString path, paths) + { + QDir dir(path); + foreach (QString file, dir.entryList(nameFilters)) + { + lib.setFileName(path+"/"+file); + if (lib.load()) + break; + } + if (lib.isLoaded()) + break; + } + + return lib.isLoaded(); +} + +QFunctionPointer PluginSymbolResolver::resolve(const char *symbol) +{ + return lib.resolve(symbol); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h new file mode 100644 index 0000000..da6c62e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/pluginsymbolresolver.h @@ -0,0 +1,24 @@ +#ifndef PLUGINSYMBOLRESOLVER_H +#define PLUGINSYMBOLRESOLVER_H + +#include "coreSQLiteStudio_global.h" +#include <QStringList> +#include <QLibrary> + +class API_EXPORT PluginSymbolResolver +{ + public: + PluginSymbolResolver(); + + void addFileNameMask(const QString& mask); + void addLookupSubFolder(const QString& name); + bool load(); + QFunctionPointer resolve(const char* symbol); + + private: + QStringList nameFilters; + QStringList subFolders; + QLibrary lib; +}; + +#endif // PLUGINSYMBOLRESOLVER_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp new file mode 100644 index 0000000..57c69c0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.cpp @@ -0,0 +1,52 @@ +#include "plugin.h" +#include "plugintype.h" +#include "services/pluginmanager.h" +#include <QDebug> + +PluginType::PluginType(const QString& title, const QString& form) : + title(title), configUiForm(form) +{ +} + + +PluginType::~PluginType() +{ +} + +QString PluginType::getName() const +{ + return name; +} + +void PluginType::setNativeName(const QString& nativeName) +{ + name = nativeName; + while (name.at(0).isDigit()) + name = name.mid(1); +} +QString PluginType::getTitle() const +{ + return title; +} + +QString PluginType::getConfigUiForm() const +{ + return configUiForm; +} + +QList<Plugin*> PluginType::getLoadedPlugins() const +{ + PluginType* type = const_cast<PluginType*>(this); + return PLUGINS->getLoadedPlugins(type); +} + +QStringList PluginType::getAllPluginNames() const +{ + PluginType* type = const_cast<PluginType*>(this); + return PLUGINS->getAllPluginNames(type); +} + +bool PluginType::nameLessThan(PluginType* type1, PluginType* type2) +{ + return type1->title.compare(type2->title) < 0; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h new file mode 100644 index 0000000..1002ac8 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/plugintype.h @@ -0,0 +1,63 @@ +#ifndef PLUGINTYPE_H +#define PLUGINTYPE_H + +#include "coreSQLiteStudio_global.h" +#include <QList> +#include <QString> +#include <QObject> + +class Plugin; + +template <class T> +class DefinedPluginType; + +class API_EXPORT PluginType +{ + public: + virtual ~PluginType(); + + QString getName() const; + QString getTitle() const; + QString getConfigUiForm() const; + QList<Plugin*> getLoadedPlugins() const; + QStringList getAllPluginNames() const; + + virtual bool test(Plugin* plugin) = 0; + + template <class T> + bool isForPluginType() + { + return dynamic_cast<const DefinedPluginType<T>*>(this) != nullptr; + } + + static bool nameLessThan(PluginType* type1, PluginType* type2); + + protected: + PluginType(const QString& title, const QString& form); + void setNativeName(const QString& nativeName); + + QString title; + QString configUiForm; + QString name; +}; + + +template <class T> +class DefinedPluginType : public PluginType +{ + friend class PluginManager; + + public: + bool test(Plugin* plugin) + { + return (dynamic_cast<T*>(plugin) != nullptr); + } + + protected: + DefinedPluginType(const QString& title, const QString& form) : PluginType(title, form) + { + setNativeName(typeid(T).name()); + } +}; + +#endif // PLUGINTYPE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp new file mode 100644 index 0000000..75c213f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.cpp @@ -0,0 +1,48 @@ +#include "populateconstant.h" +#include "common/unused.h" + +PopulateConstant::PopulateConstant() +{ +} + +QString PopulateConstant::getTitle() const +{ + return tr("Constant", "populate constant plugin name"); +} + +PopulateEngine*PopulateConstant::createEngine() +{ + return new PopulateConstantEngine(); +} + +bool PopulateConstantEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + return true; +} + +QVariant PopulateConstantEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return cfg.PopulateConstant.Value.get(); +} + +void PopulateConstantEngine::afterPopulating() +{ +} + +CfgMain*PopulateConstantEngine::getConfig() +{ + return &cfg; +} + +QString PopulateConstantEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateConstantConfig"); +} + +bool PopulateConstantEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h new file mode 100644 index 0000000..1061519 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.h @@ -0,0 +1,44 @@ +#ifndef POPULATECONSTANT_H +#define POPULATECONSTANT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateConstantConfig, + CFG_CATEGORY(PopulateConstant, + CFG_ENTRY(QString, Value, QString()) + ) +) + +class PopulateConstant : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateConstant(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateConstantEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateConstantConfig, cfg) +}; + +#endif // POPULATECONSTANT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui new file mode 100644 index 0000000..39e80e1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateconstant.ui @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateConstantConfig</class> + <widget class="QWidget" name="PopulateConstantConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>77</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="valueGroup"> + <property name="title"> + <string>Constant value:</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLineEdit" name="valueEdit"> + <property name="cfg" stdset="0"> + <string>PopulateConstant.Value</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp new file mode 100644 index 0000000..3d78de5 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.cpp @@ -0,0 +1,94 @@ +#include "populatedictionary.h" +#include "services/populatemanager.h" +#include "services/notifymanager.h" +#include "common/unused.h" +#include <QFileInfo> +#include <QFile> +#include <QTextStream> + +PopulateDictionary::PopulateDictionary() +{ +} + +QString PopulateDictionary::getTitle() const +{ + return tr("Dictionary", "dictionary populating plugin name"); +} + +PopulateEngine*PopulateDictionary::createEngine() +{ + return new PopulateDictionaryEngine(); +} + +bool PopulateDictionaryEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + QFile file(cfg.PopulateDictionary.File.get()); + if (!file.open(QIODevice::ReadOnly)) + { + notifyError(QObject::tr("Could not open dictionary file %1 for reading.").arg(cfg.PopulateDictionary.File.get())); + return false; + } + QTextStream stream(&file); + QString dataStr = stream.readAll(); + file.close(); + + if (cfg.PopulateDictionary.Lines.get()) + dictionary = dataStr.split("\n"); + else + dictionary = dataStr.split(QRegExp("\\s+")); + + if (dictionary.size() == 0) + dictionary << QString(); + + dictionaryPos = 0; + dictionarySize = dictionary.size(); + if (cfg.PopulateDictionary.Random.get()) + qsrand(QDateTime::currentDateTime().toTime_t()); + + return true; +} + +QVariant PopulateDictionaryEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + if (cfg.PopulateDictionary.Random.get()) + { + int r = qrand() % dictionarySize; + return dictionary[r]; + } + else + { + if (dictionaryPos >= dictionarySize) + dictionaryPos = 0; + + return dictionary[dictionaryPos++]; + } +} + +void PopulateDictionaryEngine::afterPopulating() +{ + dictionary.clear(); + dictionarySize = 0; + dictionaryPos = 0; +} + +CfgMain* PopulateDictionaryEngine::getConfig() +{ + return &cfg; +} + +QString PopulateDictionaryEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateDictionaryConfig"); +} + +bool PopulateDictionaryEngine::validateOptions() +{ + QFileInfo fi(cfg.PopulateDictionary.File.get()); + bool fileValid = fi.exists() && fi.isReadable() && !fi.isDir(); + POPULATE_MANAGER->handleValidationFromPlugin(fileValid, cfg.PopulateDictionary.File, QObject::tr("Dictionary file must exist and be readable.")); + + return fileValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h new file mode 100644 index 0000000..f5e754f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.h @@ -0,0 +1,52 @@ +#ifndef POPULATEDICTIONARY_H +#define POPULATEDICTIONARY_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +class QFile; +class QTextStream; + +CFG_CATEGORIES(PopulateDictionaryConfig, + CFG_CATEGORY(PopulateDictionary, + CFG_ENTRY(QString, File, QString()) + CFG_ENTRY(bool, Lines, false) + CFG_ENTRY(bool, Random, false) + ) +) + +class PopulateDictionary : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Dictionary") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with values from a dictionary file.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateDictionary(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateDictionaryEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateDictionaryConfig, cfg) + QStringList dictionary; + int dictionarySize = 0; + int dictionaryPos = 0; +}; + +#endif // POPULATEDICTIONARY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui new file mode 100644 index 0000000..f99491f --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatedictionary.ui @@ -0,0 +1,123 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateDictionaryConfig</class> + <widget class="QWidget" name="PopulateDictionaryConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>307</width> + <height>255</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="fileGroup"> + <property name="title"> + <string>Dictionary file</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="FileEdit" name="fileEdit" native="true"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.File</string> + </property> + <property name="dialogTitle" stdset="0"> + <string>Pick dictionary file</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="separatorGroup"> + <property name="title"> + <string>Word separator</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="ConfigRadioButton" name="whitespaceRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Lines</string> + </property> + <property name="text"> + <string>Whitespace</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="ConfigRadioButton" name="libeBreakRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Lines</string> + </property> + <property name="text"> + <string>Line break</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="methodGroup"> + <property name="title"> + <string>Method of using words</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="ConfigRadioButton" name="orderedRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Random</string> + </property> + <property name="text"> + <string>Ordered</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="ConfigRadioButton" name="randomlyRadio"> + <property name="cfg" stdset="0"> + <string>PopulateDictionary.Random</string> + </property> + <property name="text"> + <string>Randomly</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigRadioButton</class> + <extends>QRadioButton</extends> + <header>common/configradiobutton.h</header> + </customwidget> + <customwidget> + <class>FileEdit</class> + <extends>QWidget</extends> + <header>common/fileedit.h</header> + <container>1</container> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h new file mode 100644 index 0000000..1a1db43 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populateplugin.h @@ -0,0 +1,70 @@ +#ifndef POPULATEPLUGIN_H +#define POPULATEPLUGIN_H + +#include "coreSQLiteStudio_global.h" +#include "plugins/plugin.h" + +class CfgMain; +class PopulateEngine; +class Db; + +class API_EXPORT PopulatePlugin : virtual public Plugin +{ + public: + virtual PopulateEngine* createEngine() = 0; +}; + +class API_EXPORT PopulateEngine +{ + public: + virtual ~PopulateEngine() {} + + virtual bool beforePopulating(Db* db, const QString& table) = 0; + virtual QVariant nextValue(bool& nextValueError) = 0; + virtual void afterPopulating() = 0; + + /** + * @brief Provides config object that holds configuration for populating. + * @return Config object, or null if the importing with this plugin is not configurable. + */ + virtual CfgMain* getConfig() = 0; + + /** + * @brief Provides name of the form to use for configuration of this plugin in the populate dialog. + * @return Name of the form (toplevel QWidget in the ui file). + * + * If populating with this plugin is not configurable (i.e. getConfig() returns null), + * then this method is not even called, so it can return anything, just to satisfy method + * return type. In that case good idea is to always return QString::null. + * + * @see FormManager + */ + virtual QString getPopulateConfigFormName() const = 0; + + /** + * @brief Called when the UI expects any configuration options to be re-validated. + * @return true if the validation was successful, or false otherwise. + * + * When user interacts with the UI in a way that it doesn't change the config values, + * but it still requires some options to be re-validated, this method is called. + * + * It should validate any configuration values defined with CFG_CATEGORY and CFG_ENTRY + * and post the validation results by calling POPULATE_MANAGER->handleValidationFromPlugin() + * for every validated CfgEntry. + * + * This is also a good idea to connect to the CfgEntry::changed() signal for entries that should be validated + * and call this method from the slot, so any changes to the configuration values will be + * immediately validated and reflected on the UI. + * + * In this method you can also call POPULATE_MANAGER->configStateUpdateFromPlugin() to adjust options UI + * to the current config values. + * + * Apart from calling POPULATE_MANAGER with validation results, it should also return true or false, + * according to validation results. The return value is used by the PopulateDialog to tell if the plugin + * is currently configured correctly, without going into details, without handling signals from POPULATE_MANAGER. + */ + virtual bool validateOptions() = 0; +}; + + +#endif // POPULATEPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp new file mode 100644 index 0000000..3258bbc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.cpp @@ -0,0 +1,55 @@ +#include "populaterandom.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include <QDateTime> + +PopulateRandom::PopulateRandom() +{ +} + +QString PopulateRandom::getTitle() const +{ + return tr("Random number"); +} + +PopulateEngine* PopulateRandom::createEngine() +{ + return new PopulateRandomEngine(); +} + +bool PopulateRandomEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandom.MaxValue.get() - cfg.PopulateRandom.MinValue.get() + 1; + return (range > 0); +} + +QVariant PopulateRandomEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + QString randValue = QString::number((qrand() % range) + cfg.PopulateRandom.MinValue.get()); + return (cfg.PopulateRandom.Prefix.get() + randValue + cfg.PopulateRandom.Suffix.get()); +} + +void PopulateRandomEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomConfig"); +} + +bool PopulateRandomEngine::validateOptions() +{ + bool valid = (cfg.PopulateRandom.MinValue.get() <= cfg.PopulateRandom.MaxValue.get()); + POPULATE_MANAGER->handleValidationFromPlugin(valid, cfg.PopulateRandom.MaxValue, QObject::tr("Maximum value cannot be less than minimum value.")); + return valid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h new file mode 100644 index 0000000..f4e9feb --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.h @@ -0,0 +1,47 @@ +#ifndef POPULATERANDOM_H +#define POPULATERANDOM_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomConfig, + CFG_CATEGORY(PopulateRandom, + CFG_ENTRY(int, MinValue, 0) + CFG_ENTRY(int, MaxValue, 99999999) + CFG_ENTRY(QString, Prefix, QString()) + CFG_ENTRY(QString, Suffix, QString()) + ) +) + +class PopulateRandom : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random numbers.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandom(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomConfig, cfg) + int range; +}; +#endif // POPULATERANDOM_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui new file mode 100644 index 0000000..fb304ea --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandom.ui @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateRandomConfig</class> + <widget class="QWidget" name="PopulateRandomConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>400</width> + <height>144</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="2" column="0"> + <widget class="QGroupBox" name="prefixGroup"> + <property name="title"> + <string>Constant prefix</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QLineEdit" name="prefixEdit"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.Prefix</string> + </property> + <property name="placeholderText"> + <string>No prefix</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="minGroup"> + <property name="title"> + <string>Minimum value</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QSpinBox" name="minSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.MinValue</string> + </property> + <property name="minimum"> + <number>-999999999</number> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="maxGroup"> + <property name="title"> + <string>Maximum value</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QSpinBox" name="maxSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.MaxValue</string> + </property> + <property name="minimum"> + <number>-999999999</number> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + <property name="value"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QGroupBox" name="suffixGroup"> + <property name="title"> + <string>Constant suffix</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QLineEdit" name="suffixEdit"> + <property name="cfg" stdset="0"> + <string>PopulateRandom.Suffix</string> + </property> + <property name="placeholderText"> + <string>No suffix</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp new file mode 100644 index 0000000..d9f148a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.cpp @@ -0,0 +1,91 @@ +#include "populaterandomtext.h" +#include "common/utils.h" +#include "common/unused.h" +#include "services/populatemanager.h" + +PopulateRandomText::PopulateRandomText() +{ +} + +QString PopulateRandomText::getTitle() const +{ + return tr("Random text"); +} + +PopulateEngine* PopulateRandomText::createEngine() +{ + return new PopulateRandomTextEngine(); +} + +bool PopulateRandomTextEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + qsrand(QDateTime::currentDateTime().toTime_t()); + range = cfg.PopulateRandomText.MaxLength.get() - cfg.PopulateRandomText.MinLength.get() + 1; + + chars = ""; + + if (cfg.PopulateRandomText.UseCustomSets.get()) + { + chars = cfg.PopulateRandomText.CustomCharacters.get(); + } + else if (cfg.PopulateRandomText.IncludeBinary.get()) + { + for (int i = 0; i < 256; i++) + chars.append(QChar((char)i)); + } + else + { + if (cfg.PopulateRandomText.IncludeAlpha.get()) + chars += QStringLiteral("abcdefghijklmnopqrstuvwxyz"); + + if (cfg.PopulateRandomText.IncludeNumeric.get()) + chars += QStringLiteral("0123456789"); + + if (cfg.PopulateRandomText.IncludeWhitespace.get()) + chars += QStringLiteral(" \t\n"); + } + + return !chars.isEmpty(); +} + +QVariant PopulateRandomTextEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + int lgt = (qrand() % range) + cfg.PopulateRandomText.MinLength.get(); + return randStr(lgt, chars); +} + +void PopulateRandomTextEngine::afterPopulating() +{ +} + +CfgMain* PopulateRandomTextEngine::getConfig() +{ + return &cfg; +} + +QString PopulateRandomTextEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateRandomTextConfig"); +} + +bool PopulateRandomTextEngine::validateOptions() +{ + bool rangeValid = (cfg.PopulateRandomText.MinLength.get() <= cfg.PopulateRandomText.MaxLength.get()); + POPULATE_MANAGER->handleValidationFromPlugin(rangeValid, cfg.PopulateRandomText.MaxLength, QObject::tr("Maximum length cannot be less than minimum length.")); + + bool useCustom = cfg.PopulateRandomText.UseCustomSets.get(); + bool useBinary = cfg.PopulateRandomText.IncludeBinary.get(); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeAlpha, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeNumeric, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeWhitespace, true, !useCustom && !useBinary); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.IncludeBinary, true, !useCustom); + POPULATE_MANAGER->updateVisibilityAndEnabled(cfg.PopulateRandomText.CustomCharacters, true, useCustom); + + bool customValid = !useCustom || !cfg.PopulateRandomText.CustomCharacters.get().isEmpty(); + POPULATE_MANAGER->handleValidationFromPlugin(customValid, cfg.PopulateRandomText.CustomCharacters, QObject::tr("Custom character set cannot be empty.")); + + return rangeValid && customValid; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h new file mode 100644 index 0000000..892b302 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.h @@ -0,0 +1,52 @@ +#ifndef POPULATERANDOMTEXT_H +#define POPULATERANDOMTEXT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateRandomTextConfig, + CFG_CATEGORY(PopulateRandomText, + CFG_ENTRY(int, MinLength, 4) + CFG_ENTRY(int, MaxLength, 20) + CFG_ENTRY(bool, IncludeAlpha, true) + CFG_ENTRY(bool, IncludeNumeric, true) + CFG_ENTRY(bool, IncludeWhitespace, true) + CFG_ENTRY(bool, IncludeBinary, false) + CFG_ENTRY(bool, UseCustomSets, false) + CFG_ENTRY(QString, CustomCharacters, QString()) + ) +) +class PopulateRandomText : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Random text") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with random characters.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateRandomText(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateRandomTextEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateRandomTextConfig, cfg) + int range; + QString chars; +}; + +#endif // POPULATERANDOMTEXT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui new file mode 100644 index 0000000..28febde --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populaterandomtext.ui @@ -0,0 +1,181 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateRandomTextConfig</class> + <widget class="QWidget" name="PopulateRandomTextConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>367</width> + <height>291</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <widget class="ConfigRadioButton" name="commonSetRadio"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.UseCustomSets</string> + </property> + <property name="text"> + <string>Use characters from common sets:</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="assignedValue" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="minLengthGroup"> + <property name="title"> + <string>Minimum length</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QSpinBox" name="minLengthSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.MinLength</string> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QFrame" name="commonSetFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QCheckBox" name="alphaCheck"> + <property name="toolTip"> + <string>Letters from a to z.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeAlpha</string> + </property> + <property name="text"> + <string>Alpha</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="numericCheck"> + <property name="toolTip"> + <string>Numbers from 0 to 9.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeNumeric</string> + </property> + <property name="text"> + <string>Numeric</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="whitespaceCheck"> + <property name="toolTip"> + <string>A whitespace, a tab and a new line character.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeWhitespace</string> + </property> + <property name="text"> + <string>Whitespace</string> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="binaryCheck"> + <property name="toolTip"> + <string>Includes all above and all others.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.IncludeBinary</string> + </property> + <property name="text"> + <string>Binary</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="3" column="0" colspan="2"> + <widget class="ConfigRadioButton" name="customSetRadio"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.UseCustomSets</string> + </property> + <property name="text"> + <string>Use characters from my custom set:</string> + </property> + <property name="assignedValue" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="maxLengthGroup"> + <property name="title"> + <string>Maximum length</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QSpinBox" name="maxLengthSpin"> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.MaxLength</string> + </property> + <property name="maximum"> + <number>999999999</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="4" column="0" colspan="2"> + <widget class="QFrame" name="customSetFrame"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QLineEdit" name="customSetEdit"> + <property name="toolTip"> + <string>If you type some character multiple times, it's more likely to be used.</string> + </property> + <property name="cfg" stdset="0"> + <string>PopulateRandomText.CustomCharacters</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>ConfigRadioButton</class> + <extends>QRadioButton</extends> + <header>common/configradiobutton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp new file mode 100644 index 0000000..79a8ac1 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.cpp @@ -0,0 +1,125 @@ +#include "populatescript.h" +#include "common/unused.h" +#include "services/populatemanager.h" +#include "services/pluginmanager.h" +#include "services/notifymanager.h" + +PopulateScript::PopulateScript() +{ +} + +QString PopulateScript::getTitle() const +{ + return tr("Script"); +} + +PopulateEngine* PopulateScript::createEngine() +{ + return new PopulateScriptEngine(); +} + +bool PopulateScriptEngine::beforePopulating(Db* db, const QString& table) +{ + this->db = db; + this->table = table; + + evalArgs = {db->getName(), table}; + + scriptingPlugin = nullptr; + for (ScriptingPlugin* plugin : PLUGINS->getLoadedPlugins<ScriptingPlugin>()) + { + if (plugin->getLanguage() == cfg.PopulateScript.Language.get()) + { + scriptingPlugin = plugin; + break; + } + } + + if (!scriptingPlugin) + { + notifyError(QObject::tr("Could not find plugin to support scripting language: %1").arg(cfg.PopulateScript.Language.get())); + return false; + } + + dbAwarePlugin = dynamic_cast<DbAwareScriptingPlugin*>(scriptingPlugin); + + context = scriptingPlugin->createContext(); + + QString initCode = cfg.PopulateScript.InitCode.get(); + if (!initCode.trimmed().isEmpty()) + { + if (dbAwarePlugin) + dbAwarePlugin->evaluate(context, initCode, evalArgs, db); + else + scriptingPlugin->evaluate(context, initCode, evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating initial code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + return false; + } + } + + rowCnt = 1; + evalArgs << rowCnt; + + return true; +} + +QVariant PopulateScriptEngine::nextValue(bool& nextValueError) +{ + QVariant result; + if (dbAwarePlugin) + result = dbAwarePlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs, db); + else + result = scriptingPlugin->evaluate(context, cfg.PopulateScript.Code.get(), evalArgs); + + if (scriptingPlugin->hasError(context)) + { + notifyError(QObject::tr("Error while executing populating code: %1").arg(scriptingPlugin->getErrorMessage(context))); + releaseContext(); + nextValueError = true; + return QVariant(); + } + + evalArgs[2] = ++rowCnt; + + return result; +} + +void PopulateScriptEngine::afterPopulating() +{ + releaseContext(); +} + +CfgMain* PopulateScriptEngine::getConfig() +{ + return &cfg; +} + +QString PopulateScriptEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateScriptConfig"); +} + +bool PopulateScriptEngine::validateOptions() +{ + bool langValid = !cfg.PopulateScript.Language.get().isEmpty(); + bool codeValid = !cfg.PopulateScript.Code.get().trimmed().isEmpty(); + QString lang = cfg.PopulateScript.Language.get(); + + POPULATE_MANAGER->handleValidationFromPlugin(langValid, cfg.PopulateScript.Language, QObject::tr("Select implementation language.")); + POPULATE_MANAGER->handleValidationFromPlugin(codeValid, cfg.PopulateScript.Code, QObject::tr("Implementation code cannot be empty.")); + + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.InitCode, PluginServiceBase::LANG_PROPERTY_NAME, lang); + POPULATE_MANAGER->propertySetFromPlugin(cfg.PopulateScript.Code, PluginServiceBase::LANG_PROPERTY_NAME, lang); + + return langValid && codeValid; +} + +void PopulateScriptEngine::releaseContext() +{ + scriptingPlugin->releaseContext(context); + context = nullptr; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h new file mode 100644 index 0000000..8870b67 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.h @@ -0,0 +1,63 @@ +#ifndef POPULATESCRIPT_H +#define POPULATESCRIPT_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" +#include "scriptingplugin.h" + +CFG_CATEGORIES(PopulateScriptConfig, + CFG_CATEGORY(PopulateScript, + CFG_ENTRY(QString, Language, QString()) + CFG_ENTRY(QString, InitCode, QString()) + CFG_ENTRY(QString, Code, QString()) + ) +) + +/** + * @brief Populate from evaluated script code + * + * Initial code evaluation gets 2 arguments - the db name and the table name. + * Each evaluation of per-step code gets 3 arguments, 2 of them just like above + * and the 3rd is number of the row being currently populated (starting from 1). + */ +class PopulateScript : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Constant") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with a constant value.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateScript(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateScriptEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateScriptConfig, cfg) + void releaseContext(); + + ScriptingPlugin* scriptingPlugin = nullptr; + DbAwareScriptingPlugin* dbAwarePlugin = nullptr; + ScriptingPlugin::Context* context = nullptr; + Db* db = nullptr; + QString table; + int rowCnt = 0; + QList<QVariant> evalArgs; +}; + +#endif // POPULATESCRIPT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui new file mode 100644 index 0000000..8d37994 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatescript.ui @@ -0,0 +1,112 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateScriptConfig</class> + <widget class="QWidget" name="PopulateScriptConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>443</width> + <height>362</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <property name="initialSize" stdset="0"> + <size> + <width>440</width> + <height>360</height> + </size> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0" colspan="2"> + <widget class="QGroupBox" name="initCodeGroup"> + <property name="title"> + <string>Initialization code (optional)</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="QPlainTextEdit" name="initCodeEdit"> + <property name="cfg" stdset="0"> + <string>PopulateScript.InitCode</string> + </property> + <property name="scriptingEdit" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0" colspan="2"> + <widget class="QGroupBox" name="codeGroup"> + <property name="title"> + <string>Per step code</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QPlainTextEdit" name="codeEdit"> + <property name="cfg" stdset="0"> + <string>PopulateScript.Code</string> + </property> + <property name="scriptingEdit" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="0"> + <widget class="QGroupBox" name="langGroup"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Language</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <item> + <widget class="QComboBox" name="langCombo"> + <property name="cfg" stdset="0"> + <string>PopulateScript.Language</string> + </property> + <property name="ScriptingLangCombo" stdset="0"> + <bool>true</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="0" column="1"> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Help</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <item> + <widget class="QToolButton" name="toolButton"> + <property name="text"> + <string>...</string> + </property> + <property name="openUrl" stdset="0"> + <string>http://sqlitestudio.pl/wiki/index.php/Official_plugins#Script_.28built-in.29</string> + </property> + <property name="customIcon" stdset="0"> + <string>help</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp new file mode 100644 index 0000000..a0ad94e --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.cpp @@ -0,0 +1,53 @@ +#include "populatesequence.h" +#include "common/global.h" +#include "services/populatemanager.h" +#include "common/unused.h" +#include <QVariant> + +PopulateSequence::PopulateSequence() +{ +} + +QString PopulateSequence::getTitle() const +{ + return tr("Sequence"); +} + +PopulateEngine* PopulateSequence::createEngine() +{ + return new PopulateSequenceEngine(); +} + +bool PopulateSequenceEngine::beforePopulating(Db* db, const QString& table) +{ + UNUSED(db); + UNUSED(table); + seq = cfg.PopulateSequence.StartValue.get(); + step = cfg.PopulateSequence.Step.get(); + return true; +} + +QVariant PopulateSequenceEngine::nextValue(bool& nextValueError) +{ + UNUSED(nextValueError); + return seq += step; +} + +void PopulateSequenceEngine::afterPopulating() +{ +} + +CfgMain* PopulateSequenceEngine::getConfig() +{ + return &cfg; +} + +QString PopulateSequenceEngine::getPopulateConfigFormName() const +{ + return QStringLiteral("PopulateSequenceConfig"); +} + +bool PopulateSequenceEngine::validateOptions() +{ + return true; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h new file mode 100644 index 0000000..5435477 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.h @@ -0,0 +1,47 @@ +#ifndef POPULATESEQUENCE_H +#define POPULATESEQUENCE_H + +#include "builtinplugin.h" +#include "populateplugin.h" +#include "config_builder.h" + +CFG_CATEGORIES(PopulateSequenceConfig, + CFG_CATEGORY(PopulateSequence, + CFG_ENTRY(int, StartValue, 0) + CFG_ENTRY(int, Step, 1) + ) +) + +class PopulateSequence : public BuiltInPlugin, public PopulatePlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Sequence") + SQLITESTUDIO_PLUGIN_DESC("Support for populating tables with sequenced values.") + SQLITESTUDIO_PLUGIN_VERSION(10001) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + PopulateSequence(); + + QString getTitle() const; + PopulateEngine* createEngine(); +}; + +class PopulateSequenceEngine : public PopulateEngine +{ + public: + bool beforePopulating(Db* db, const QString& table); + QVariant nextValue(bool& nextValueError); + void afterPopulating(); + CfgMain* getConfig(); + QString getPopulateConfigFormName() const; + bool validateOptions(); + + private: + CFG_LOCAL(PopulateSequenceConfig, cfg) + qint64 seq = 0; + qint64 step = 1; +}; + +#endif // POPULATESEQUENCE_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui new file mode 100644 index 0000000..231af85 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/populatesequence.ui @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>PopulateSequenceConfig</class> + <widget class="QWidget" name="PopulateSequenceConfig"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>279</width> + <height>67</height> + </rect> + </property> + <property name="windowTitle"> + <string>Form</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="1"> + <widget class="QSpinBox" name="startValueSpin"> + <property name="cfg" stdset="0"> + <string>PopulateSequence.StartValue</string> + </property> + <property name="minimum"> + <number>-99999999</number> + </property> + <property name="maximum"> + <number>99999999</number> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="startValueLabel"> + <property name="text"> + <string>Start value:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QSpinBox" name="stepSpin"> + <property name="cfg" stdset="0"> + <string>PopulateSequence.Step</string> + </property> + <property name="minimum"> + <number>-999999</number> + </property> + <property name="maximum"> + <number>999999</number> + </property> + <property name="value"> + <number>1</number> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="stepLabel"> + <property name="text"> + <string>Step:</string> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h new file mode 100644 index 0000000..d081073 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingplugin.h @@ -0,0 +1,50 @@ +#ifndef SCRIPTINGPLUGIN_H +#define SCRIPTINGPLUGIN_H + +#include "plugin.h" +#include <QVariant> + +class Db; + +class ScriptingPlugin : virtual public Plugin +{ + public: + class Context + { + public: + virtual ~Context() {} + }; + + virtual QString getLanguage() const = 0; + virtual Context* createContext() = 0; + virtual void releaseContext(Context* context) = 0; + virtual void resetContext(Context* context) = 0; + virtual void setVariable(Context* context, const QString& name, const QVariant& value) = 0; + virtual QVariant getVariable(Context* context, const QString& name) = 0; + virtual QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args = QList<QVariant>()) = 0; + virtual bool hasError(Context* context) const = 0; + virtual QString getErrorMessage(Context* context) const = 0; + virtual QVariant evaluate(const QString& code, const QList<QVariant>& args = QList<QVariant>(), QString* errorMessage = nullptr) = 0; + virtual QString getIconPath() const = 0; +}; + +class DbAwareScriptingPlugin : public ScriptingPlugin +{ + public: + virtual QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking = false) = 0; + virtual QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr) = 0; + + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args) + { + return evaluate(context, code, args, nullptr, true); + } + + QVariant evaluate(const QString& code, const QList<QVariant>& args, QString* errorMessage = nullptr) + { + return evaluate(code, args, nullptr, true, errorMessage); + } +}; + +Q_DECLARE_METATYPE(ScriptingPlugin::Context*) + +#endif // SCRIPTINGPLUGIN_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp new file mode 100644 index 0000000..334c0cc --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.cpp @@ -0,0 +1,276 @@ +#include "scriptingqt.h" +#include "common/unused.h" +#include "common/global.h" +#include "scriptingqtdbproxy.h" +#include <QScriptEngine> +#include <QMutex> +#include <QMutexLocker> +#include <QDebug> + +static QScriptValue scriptingQtDebug(QScriptContext *context, QScriptEngine *engine) +{ + UNUSED(engine); + QStringList args; + for (int i = 0; i < context->argumentCount(); i++) + args << context->argument(i).toString(); + + qDebug() << "[ScriptingQt]" << args; + return QScriptValue(); +} + +ScriptingQt::ScriptingQt() +{ + mainEngineMutex = new QMutex(); +} + +ScriptingQt::~ScriptingQt() +{ + safe_delete(mainEngineMutex); +} + +QString ScriptingQt::getLanguage() const +{ + return QStringLiteral("QtScript"); +} + +ScriptingPlugin::Context* ScriptingQt::createContext() +{ + ContextQt* ctx = new ContextQt; + ctx->engine->pushContext(); + contexts << ctx; + return ctx; +} + +void ScriptingQt::releaseContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + contexts.removeOne(ctx); + delete ctx; +} + +void ScriptingQt::resetContext(ScriptingPlugin::Context* context) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->popContext(); + ctx->engine->pushContext(); +} + +QVariant ScriptingQt::evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage) +{ + QMutexLocker locker(mainEngineMutex); + + // Enter a new context + QScriptContext* engineContext = mainContext->engine->pushContext(); + + // Call the function + QVariant result = evaluate(mainContext, engineContext, code, args, db, locking); + + // Handle errors + if (!mainContext->error.isEmpty()) + *errorMessage = mainContext->error; + + // Leave the context to reset "this". + mainContext->engine->popContext(); + + return result; +} + +QVariant ScriptingQt::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + return evaluate(ctx, ctx->engine->currentContext(), code, args, db, locking); +} + +QVariant ScriptingQt::evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + // Define function to call + QScriptValue functionValue = getFunctionValue(ctx, code); + + // Db for this evaluation + ctx->dbProxy->setDb(db); + ctx->dbProxy->setUseDbLocking(locking); + + // Call the function + QScriptValue result; + if (args.size() > 0) + result = functionValue.call(engineContext->activationObject(), ctx->engine->toScriptValue(args)); + else + result = functionValue.call(engineContext->activationObject()); + + // Handle errors + ctx->error.clear(); + if (ctx->engine->hasUncaughtException()) + ctx->error = ctx->engine->uncaughtException().toString(); + + ctx->dbProxy->setDb(nullptr); + ctx->dbProxy->setUseDbLocking(false); + + return convertVariant(result.toVariant()); +} + +QVariant ScriptingQt::convertVariant(const QVariant& value, bool wrapStrings) +{ + switch (value.type()) + { + case QVariant::Hash: + { + QHash<QString, QVariant> hash = value.toHash(); + QHashIterator<QString, QVariant> it(hash); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::Map: + { + QMap<QString, QVariant> map = value.toMap(); + QMapIterator<QString, QVariant> it(map); + QStringList list; + while (it.hasNext()) + { + it.next(); + list << it.key() + ": " + convertVariant(it.value(), true).toString(); + } + return "{" + list.join(", ") + "}"; + } + case QVariant::List: + { + QStringList list; + for (const QVariant& var : value.toList()) + list << convertVariant(var, true).toString(); + + return "[" + list.join(", ") + "]"; + } + case QVariant::StringList: + { + return "[\"" + value.toStringList().join("\", \"") + "\"]"; + } + case QVariant::String: + { + if (wrapStrings) + return "\"" + value.toString() + "\""; + + break; + } + default: + break; + } + return value; +} + +void ScriptingQt::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return; + + ctx->engine->globalObject().setProperty(name, ctx->engine->newVariant(value)); +} + +QVariant ScriptingQt::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QVariant(); + + QScriptValue value = ctx->engine->globalObject().property(name); + return convertVariant(value.toVariant()); +} + +bool ScriptingQt::hasError(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return false; + + return !ctx->error.isEmpty(); +} + +QString ScriptingQt::getErrorMessage(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = getContext(context); + if (!ctx) + return QString::null; + + return ctx->error; +} + +QString ScriptingQt::getIconPath() const +{ + return ":/images/plugins/scriptingqt.png"; +} + +bool ScriptingQt::init() +{ + QMutexLocker locker(mainEngineMutex); + mainContext = new ContextQt; + return true; +} + +void ScriptingQt::deinit() +{ + foreach (Context* ctx, contexts) + delete ctx; + + contexts.clear(); + + QMutexLocker locker(mainEngineMutex); + safe_delete(mainContext); +} + +ScriptingQt::ContextQt* ScriptingQt::getContext(ScriptingPlugin::Context* context) const +{ + ContextQt* ctx = dynamic_cast<ContextQt*>(context); + if (!ctx) + qDebug() << "Invalid context passed to ScriptingQt:" << context; + + return ctx; +} + +QScriptValue ScriptingQt::getFunctionValue(ContextQt* ctx, const QString& code) +{ + static const QString fnDef = QStringLiteral("(function () {%1\n})"); + + QScriptProgram* prog = nullptr; + if (!ctx->scriptCache.contains(code)) + { + prog = new QScriptProgram(fnDef.arg(code)); + ctx->scriptCache.insert(code, prog); + } + else + { + prog = ctx->scriptCache[code]; + } + return ctx->engine->evaluate(*prog); +} + +ScriptingQt::ContextQt::ContextQt() +{ + engine = new QScriptEngine(); + + dbProxy = new ScriptingQtDbProxy(); + dbProxyScriptValue = engine->newQObject(dbProxy, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater); + + engine->globalObject().setProperty("debug", engine->newFunction(scriptingQtDebug)); + engine->globalObject().setProperty("db", dbProxyScriptValue); + + scriptCache.setMaxCost(cacheSize); +} + +ScriptingQt::ContextQt::~ContextQt() +{ + safe_delete(engine); + safe_delete(dbProxy); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h new file mode 100644 index 0000000..125789a --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.h @@ -0,0 +1,72 @@ +#ifndef SCRIPTINGQT_H +#define SCRIPTINGQT_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" +#include <QHash> +#include <QVariant> +#include <QCache> +#include <QScriptValue> +#include <QScriptProgram> + +class QScriptEngine; +class QMutex; +class QScriptContext; +class ScriptingQtDbProxy; + +class ScriptingQt : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("Qt scripting") + SQLITESTUDIO_PLUGIN_DESC("Qt scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + ScriptingQt(); + ~ScriptingQt(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking = false, QString* errorMessage = nullptr); + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking = false); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + using DbAwareScriptingPlugin::evaluate; + + class ContextQt : public ScriptingPlugin::Context + { + public: + ContextQt(); + ~ContextQt(); + + QScriptEngine* engine = nullptr; + QCache<QString,QScriptProgram> scriptCache; + QString error; + ScriptingQtDbProxy* dbProxy = nullptr; + QScriptValue dbProxyScriptValue; + }; + + ContextQt* getContext(ScriptingPlugin::Context* context) const; + QScriptValue getFunctionValue(ContextQt* ctx, const QString& code); + QVariant evaluate(ContextQt* ctx, QScriptContext* engineContext, const QString& code, const QList<QVariant>& args, Db* db, bool locking); + QVariant convertVariant(const QVariant& value, bool wrapStrings = false); + + static const constexpr int cacheSize = 5; + + ContextQt* mainContext = nullptr; + QList<Context*> contexts; + QMutex* mainEngineMutex = nullptr; +}; + +#endif // SCRIPTINGQT_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png Binary files differnew file mode 100644 index 0000000..220ea27 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqt.png diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp new file mode 100644 index 0000000..ff3c7ee --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.cpp @@ -0,0 +1,145 @@ +#include "scriptingqtdbproxy.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include <QScriptContext> +#include <QScriptEngine> + +ScriptingQtDbProxy::ScriptingQtDbProxy(QObject *parent) : + QObject(parent) +{ +} +Db* ScriptingQtDbProxy::getDb() const +{ + return db; +} + +void ScriptingQtDbProxy::setDb(Db* value) +{ + db = value; +} +bool ScriptingQtDbProxy::getUseDbLocking() const +{ + return useDbLocking; +} + +void ScriptingQtDbProxy::setUseDbLocking(bool value) +{ + useDbLocking = value; +} + +QHash<QString, QVariant> ScriptingQtDbProxy::mapToHash(const QMap<QString, QVariant>& map) +{ + QHash<QString, QVariant> hash; + QMapIterator<QString, QVariant> it(map); + while (it.hasNext()) + { + it.next(); + hash[it.key()] = it.value(); + } + return hash; +} + +QVariant ScriptingQtDbProxy::evalInternal(const QString& sql, const QList<QVariant>& listArgs, const QMap<QString, QVariant>& mapArgs, + bool singleCell, const QScriptValue* funcPtr) +{ + if (!db) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("No database available in current context, while called QtScript's %1 command.").arg(funcName)); + return evalInternalErrorResult(singleCell); + } + + Db::Flags flags; + if (!useDbLocking) + flags |= Db::Flag::NO_LOCK; + + SqlQueryPtr results; + if (listArgs.size() > 0) + results = db->exec(sql, listArgs, flags); + else + results = db->exec(sql, mapToHash(mapArgs), flags); + + if (results->isError()) + { + QString funcName = singleCell ? QStringLiteral("db.onecolumn()") : QStringLiteral("db.eval()"); + context()->throwError(tr("Error from %1: %2").arg(funcName, results->getErrorText())); + return evalInternalErrorResult(singleCell); + } + + if (singleCell) + { + return results->getSingleCell(); + } + else if (funcPtr) + { + QScriptValue func(*funcPtr); + SqlResultsRowPtr row; + QScriptValue funcArgs; + QScriptValue funcResult; + while (results->hasNext()) + { + row = results->next(); + funcArgs = context()->engine()->toScriptValue(row->valueList()); + funcResult = func.call(context()->thisObject(), funcArgs); + if (!funcResult.isUndefined()) + break; + } + return funcResult.toVariant(); + } + else + { + QList<QVariant> evalResults; + SqlResultsRowPtr row; + while (results->hasNext()) + { + row = results->next(); + evalResults << QVariant(row->valueList()); + } + return evalResults; + } +} + +QVariant ScriptingQtDbProxy::evalInternalErrorResult(bool singleCell) +{ + QList<QVariant> result; + if (singleCell) + result << QVariant(); + + return result; +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql) +{ + return evalInternal(sql, QList<QVariant>(), QMap<QString, QVariant>(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args) +{ + return evalInternal(sql, QList<QVariant>(), args, false); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), false, &func); +} + +QVariant ScriptingQtDbProxy::eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func) +{ + return evalInternal(sql, QList<QVariant>(), args, false, &func); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QList<QVariant>& args) +{ + return evalInternal(sql, args, QMap<QString, QVariant>(), true); +} + +QVariant ScriptingQtDbProxy::onecolumn(const QString& sql, const QMap<QString, QVariant>& args) +{ + return evalInternal(sql, QList<QVariant>(), args, true); +} + diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h new file mode 100644 index 0000000..add9540 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingqtdbproxy.h @@ -0,0 +1,44 @@ +#ifndef SCRIPTINGQTDBPROXY_H +#define SCRIPTINGQTDBPROXY_H + +#include <QObject> +#include <QScriptable> +#include <QHash> +#include <QList> +#include <QVariant> + +class Db; + +class ScriptingQtDbProxy : public QObject, protected QScriptable +{ + Q_OBJECT + public: + explicit ScriptingQtDbProxy(QObject *parent = 0); + + Db* getDb() const; + void setDb(Db* value); + + bool getUseDbLocking() const; + void setUseDbLocking(bool value); + + private: + QVariant evalInternal(const QString& sql, const QList<QVariant>& listArgs, const QMap<QString, QVariant>& mapArgs, bool singleCell, + const QScriptValue* funcPtr = nullptr); + QVariant evalInternalErrorResult(bool singleCell); + + static QHash<QString, QVariant> mapToHash(const QMap<QString, QVariant>& map); + + Db* db = nullptr; + bool useDbLocking = false; + + public slots: + QVariant eval(const QString& sql); + QVariant eval(const QString& sql, const QList<QVariant>& args); + QVariant eval(const QString& sql, const QMap<QString, QVariant>& args); + QVariant eval(const QString& sql, const QList<QVariant>& args, const QScriptValue& func); + QVariant eval(const QString& sql, const QMap<QString, QVariant>& args, const QScriptValue& func); + QVariant onecolumn(const QString& sql, const QList<QVariant>& args); + QVariant onecolumn(const QString& sql, const QMap<QString, QVariant>& args); +}; + +#endif // SCRIPTINGQTDBPROXY_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp new file mode 100644 index 0000000..93a6d91 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.cpp @@ -0,0 +1,146 @@ +#include "scriptingsql.h" +#include "common/unused.h" +#include "db/db.h" +#include "db/sqlquery.h" +#include "services/dbmanager.h" + +ScriptingSql::ScriptingSql() +{ +} + +ScriptingSql::~ScriptingSql() +{ +} + +QString ScriptingSql::getLanguage() const +{ + return "SQL"; +} + +ScriptingPlugin::Context* ScriptingSql::createContext() +{ + SqlContext* ctx = new SqlContext(); + contexts << ctx; + return ctx; +} + +void ScriptingSql::releaseContext(ScriptingPlugin::Context* context) +{ + if (!contexts.contains(context)) + return; + + delete context; + contexts.removeOne(context); +} + +void ScriptingSql::resetContext(ScriptingPlugin::Context* context) +{ + dynamic_cast<SqlContext*>(context)->errorText.clear(); +} + +QVariant ScriptingSql::evaluate(ScriptingPlugin::Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking) +{ + SqlContext* ctx = dynamic_cast<SqlContext*>(context); + ctx->errorText.clear(); + + Db* theDb = nullptr; + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + QString sql = code; + if (ctx->variables.size() > 0) + { + QString value; + for (const QString& key : ctx->variables.keys()) + { + value = "'" + ctx->variables[key].toString() + "'"; + sql.replace(":" + key, value).replace("@" + key, value).replace("$" + key, value); + } + } + + SqlQueryPtr result = theDb->exec(sql, args, execFlags); + if (result->isError()) + { + dynamic_cast<SqlContext*>(context)->errorText = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +QVariant ScriptingSql::evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage) +{ + Db* theDb = nullptr; + + if (db && db->isValid()) + theDb = db; + else if (memDb) + theDb = memDb; + else + return QVariant(); + + Db::Flags execFlags; + if (!locking) + execFlags |= Db::Flag::NO_LOCK; + + SqlQueryPtr result = theDb->exec(code, args, execFlags); + if (result->isError()) + { + *errorMessage = result->getErrorText(); + return QVariant(); + } + + return result->getSingleCell(); +} + +void ScriptingSql::setVariable(ScriptingPlugin::Context* context, const QString& name, const QVariant& value) +{ + dynamic_cast<SqlContext*>(context)->variables[name] = value; +} + +QVariant ScriptingSql::getVariable(ScriptingPlugin::Context* context, const QString& name) +{ + if (dynamic_cast<SqlContext*>(context)->variables.contains(name)) + return dynamic_cast<SqlContext*>(context)->variables[name]; + + return QVariant(); +} + +bool ScriptingSql::hasError(ScriptingPlugin::Context* context) const +{ + return !getErrorMessage(context).isNull(); +} + +QString ScriptingSql::getErrorMessage(ScriptingPlugin::Context* context) const +{ + return dynamic_cast<SqlContext*>(context)->errorText; +} + +QString ScriptingSql::getIconPath() const +{ + return ":/images/plugins/scriptingsql.png"; +} + +bool ScriptingSql::init() +{ + memDb = DBLIST->createInMemDb(); + return memDb != nullptr; +} + +void ScriptingSql::deinit() +{ + for (Context* context : contexts) + delete context; + + contexts.clear(); + + safe_delete(memDb); +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h new file mode 100644 index 0000000..7b8fd3b --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.h @@ -0,0 +1,46 @@ +#ifndef SCRIPTINGSQL_H +#define SCRIPTINGSQL_H + +#include "builtinplugin.h" +#include "scriptingplugin.h" + +class ScriptingSql : public BuiltInPlugin, public DbAwareScriptingPlugin +{ + Q_OBJECT + + SQLITESTUDIO_PLUGIN_TITLE("SQL scripting") + SQLITESTUDIO_PLUGIN_DESC("SQL scripting support.") + SQLITESTUDIO_PLUGIN_VERSION(10000) + SQLITESTUDIO_PLUGIN_AUTHOR("sqlitestudio.pl") + + public: + class SqlContext : public Context + { + public: + QString errorText; + QHash<QString,QVariant> variables; + }; + + ScriptingSql(); + ~ScriptingSql(); + + QString getLanguage() const; + Context* createContext(); + void releaseContext(Context* context); + void resetContext(Context* context); + QVariant evaluate(Context* context, const QString& code, const QList<QVariant>& args, Db* db, bool locking); + QVariant evaluate(const QString& code, const QList<QVariant>& args, Db* db, bool locking, QString* errorMessage); + void setVariable(Context* context, const QString& name, const QVariant& value); + QVariant getVariable(Context* context, const QString& name); + bool hasError(Context* context) const; + QString getErrorMessage(Context* context) const; + QString getIconPath() const; + bool init(); + void deinit(); + + private: + QList<Context*> contexts; + Db* memDb = nullptr; +}; + +#endif // SCRIPTINGSQL_H diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png Binary files differnew file mode 100644 index 0000000..ea232a7 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/scriptingsql.png diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp new file mode 100644 index 0000000..5e1d610 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.cpp @@ -0,0 +1,29 @@ +#include "sqlformatterplugin.h" +#include "parser/parser.h" +#include "db/db.h" +#include <QDebug> + +QString SqlFormatterPlugin::format(const QString& code, Db* contextDb) +{ + Dialect dialect = Dialect::Sqlite3; + if (contextDb && contextDb->isValid()) + contextDb->getDialect(); + + Parser parser(dialect); + if (!parser.parse(code)) + { + qWarning() << "Could not parse SQL in order to format it. The SQL was:" << code; + return code; + } + + QStringList formattedQueries; + foreach (SqliteQueryPtr query, parser.getQueries()) + formattedQueries << format(query); + + return formattedQueries.join("\n"); +} + +QString SqlFormatterPlugin::getLanguage() const +{ + return "sql"; +} diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h new file mode 100644 index 0000000..cb500f6 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/sqlformatterplugin.h @@ -0,0 +1,16 @@ +#ifndef SQLFORMATTERPLUGIN_H
+#define SQLFORMATTERPLUGIN_H
+
+#include "coreSQLiteStudio_global.h"
+#include "codeformatterplugin.h"
+#include "parser/ast/sqlitequery.h"
+
+class API_EXPORT SqlFormatterPlugin : public CodeFormatterPlugin
+{
+ public:
+ QString format(const QString& code, Db* contextDb);
+ QString getLanguage() const;
+ virtual QString format(SqliteQueryPtr query) = 0;
+};
+
+#endif // SQLFORMATTERPLUGIN_H
diff --git a/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h new file mode 100644 index 0000000..c9af8e0 --- /dev/null +++ b/SQLiteStudio3/coreSQLiteStudio/plugins/uiconfiguredplugin.h @@ -0,0 +1,56 @@ +#ifndef UICONFIGUREDPLUGIN_H +#define UICONFIGUREDPLUGIN_H + +#include "coreSQLiteStudio_global.h" + +class API_EXPORT UiConfiguredPlugin +{ + public: + /** + * @brief Gets name of the configuration UI form. + * @return Name of the form object. + * + * Some plugins may link (during compilation) only to the coreSQLiteStudio part of the application, but they can still + * benefit from SQLiteStudio GUI application by providing UI form that will be used in ConfigDialog. + * + * This method should return the object name of the top-most widget found in the provided *.ui file. + * + * For more details see: http://wiki.sqlitestudio.pl/index.php/Plugin_UI_forms + */ + virtual QString getConfigUiForm() const = 0; + + /** + * @brief Provides config object for ConfigDialog. + * @return Config used by the plugin, or null when there's no config, or when config should not be configured with property binding. + * + * When this method returns null, but getConfigUiForm() returns existing form, then configuration is assumed to be kept + * in global CfgMain object (which is known to application, as all global CfgMain objects). In this case configuration is loaded/stored + * using initial and final calls to load/store values from the form. This is different from when this method returns not null. Keep reading. + * + * When this method returns pointer to an object, then ConfigDialog uses ConfigMapper to bind widgets from getConfigUiForm() with + * config values from CfgMain returned by this method. See ConfigMapper for details about binding. + * In this case ConfigDialog uses CfgMain::begin(), CfgMain::commit() and CfgMain::rollback() methods to make changes to the config + * transactional (when users clicks "cancel" or "apply"). + */ + virtual CfgMain* getMainUiConfig() = 0; + + /** + * @brief Notifies about ConfigDialog being just open. + * + * This is called just after the config dialog was open and all its contents are already initialized. + * This is a good moment to connect to plugin's CfgMain configuration object to listen for changes, + * so all uncommited (yet) configuration changes can be reflected by this plugin. + */ + virtual void configDialogOpen() = 0; + + /** + * @brief Notifies about ConfigDialog being closed. + * + * This is called just before the config dialog gets closed. + * This is a good moment to disconnect from configuration object and not listen to changes in the configuration anymore + * (couse config can change for example when application is starting and loading entire configuration, etc). + */ + virtual void configDialogClosed() = 0; +}; + +#endif // UICONFIGUREDPLUGIN_H |
