diff --git a/SQLiteStudio3/coreSQLiteStudio/querygenerator.cpp b/SQLiteStudio3/coreSQLiteStudio/querygenerator.cpp index af201ba58a..4f9be2c0a9 100644 --- a/SQLiteStudio3/coreSQLiteStudio/querygenerator.cpp +++ b/SQLiteStudio3/coreSQLiteStudio/querygenerator.cpp @@ -184,6 +184,36 @@ QString QueryGenerator::generateSelectFromTableOrView(Db* db, const QString& dat return tpl.arg(wrappedCols.join(", "), target, conditionStr); } +QString QueryGenerator::generateSelectFunction(const QString& function, const QStringList& columns, const QHash values) +{ + // To make SQLite evaluate every function call just once, we're placing the + // function calls in the VALUES clause (making them operate on literal arguments), e.g.: + // WITH data ([upper(a)], [upper(b)]) AS ( + // VALUES (upper('apple'), upper('banana')) + // ) SELECT * from data; + static_qstring(tpl, "WITH data (%1) AS (VALUES %2) SELECT * FROM data"); + static_qstring(functionCallTpl, "%1(\\1)"); + static_qstring(rowTpl, "(%1)"); + + // Group values into rows + QStringList valueSets = toValueSets(columns, values, function + "(%1)"); + QString valueStr = rowTpl.arg(valueSets.join("), (")); + + // Wrap given column names + QStringList wrappedCols = wrapObjNamesIfNeeded(columns); + + // Create expressions + QRegularExpression re("^(.*)$"); + QStringList expressions; + for (QString col : wrappedCols) + expressions << col.replace(re, functionCallTpl.arg(function)); + + // Use expressions as column names + QStringList resultWrappedCols = wrapObjNamesIfNeeded(expressions); + + return tpl.arg(resultWrappedCols.join(", "), valueStr); +} + QString QueryGenerator::getAlias(const QString& name, QSet& usedAliases) { static_qstring(tpl, "%2%1"); @@ -259,7 +289,8 @@ QString QueryGenerator::toFullObjectName(const QString& database, const QString& return tpl.arg(dbName, wrapObjIfNeeded(object)); } -QStringList QueryGenerator::toValueSets(const QStringList& columns, const StrHash values) +QStringList QueryGenerator::toValueSets(const QStringList& columns, const StrHash values, + const QString& format) { QStringList rows; QVariantList rowValues; @@ -272,7 +303,14 @@ QStringList QueryGenerator::toValueSets(const QStringList& columns, const StrHas rowValues << values[col][i]; valueList = valueListToSqlList(rowValues); - rows << valueList.join(", "); + QString row; + for (QString value : valueList) + { + if (row.size() > 0) + row.append(", "); + row.append(format.isEmpty() ? value : format.arg(value)); + } + rows << row; } return rows; diff --git a/SQLiteStudio3/coreSQLiteStudio/querygenerator.h b/SQLiteStudio3/coreSQLiteStudio/querygenerator.h index 1de8440d19..3623e17004 100644 --- a/SQLiteStudio3/coreSQLiteStudio/querygenerator.h +++ b/SQLiteStudio3/coreSQLiteStudio/querygenerator.h @@ -61,6 +61,15 @@ class API_EXPORT QueryGenerator */ QString generateSelectFromSelect(Db* db, const QString& initialSelect, const StrHash values = StrHash(), const BiStrHash& dbNameToAttach = BiStrHash()); + /** + * @brief Generates SELECT of a function applied to values + * @param function The function name to apply to every value. + * @param columns Ordered column names. + * @param values Map of column names and values for them, for generating a VALUES clause. + * @return Generated SELECT statement string. + */ + QString generateSelectFunction(const QString& function, const QStringList& columns, const QHash values); + private: QString generateSelectFromTableOrView(Db* db, const QString& database, const QString& tableOrView, const QStringList& columns, const StrHash values = StrHash()); QString getAlias(const QString& name, QSet& usedAliases); @@ -68,7 +77,8 @@ class API_EXPORT QueryGenerator QString valuesToConditionStr(const StrHash& values); QString toResultColumnString(const SelectResolver::Column& column); QString toFullObjectName(const QString& database, const QString& object); - QStringList toValueSets(const QStringList& columns, const StrHash values); + QStringList toValueSets(const QStringList& columns, const StrHash values, + const QString& format = QString()); }; #endif // QUERYGENERATOR_H diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp index 4490959b0d..d10c6d12d9 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp @@ -724,6 +724,17 @@ QString SqlQueryModel::generateSelectQueryForItems(const QList& i return sql; } +QString SqlQueryModel::generateSelectFunctionQueryForItems(const QString& function, const QList& items) +{ + QHash values = toValuesGroupedByColumns(items); + QStringList orderedColumns = toOrderedColumnNames(items); + + QueryGenerator generator; + QString sql = generator.generateSelectFunction(function, orderedColumns, values); + + return sql; +} + QString SqlQueryModel::generateInsertQueryForItems(const QList& items) { UNUSED(items); @@ -1178,6 +1189,22 @@ QHash SqlQueryModel::toValuesGroupedByColumns(const QList return values; } +QStringList SqlQueryModel::toOrderedColumnNames(const QList& items) +{ + QStringList cols; + int row = -1; + QMap> itemsByRow; + for (SqlQueryItem* item : items) + { + if (row != -1 && item->row() != row) + break; + row = item->row(); + cols << item->getColumn()->displayName; + } + + return cols; +} + bool SqlQueryModel::supportsModifyingQueriesInMenu() const { return false; diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h index 43222f1987..f645bbcf47 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h @@ -76,6 +76,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel */ void setAsyncMode(bool enabled); virtual QString generateSelectQueryForItems(const QList& items); + virtual QString generateSelectFunctionQueryForItems(const QString& function, const QList& items); virtual QString generateInsertQueryForItems(const QList& items); virtual QString generateUpdateQueryForItems(const QList& items); virtual QString generateDeleteQueryForItems(const QList& items); @@ -310,6 +311,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel RowId getNewRowId(const RowId& currentRowId, const QList items); void updateRowIdForAllItems(const AliasedTable& table, const RowId& rowId, const RowId& newRowId); QHash toValuesGroupedByColumns(const QList& items); + QStringList toOrderedColumnNames(const QList& items); void refreshGeneratedColumns(const QList& items); void refreshGeneratedColumns(const QList& items, QHash& values, const RowId& insertedRowId); diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp index 08a9b14bf9..ce0a83112d 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp @@ -10,6 +10,8 @@ #include "multieditor/multieditordialog.h" #include "uiconfig.h" #include "dialogs/sortdialog.h" +#include "sqlitestudio.h" +#include "services/functionmanager.h" #include "services/notifymanager.h" #include "windows/editorwindow.h" #include "mainwindow.h" @@ -186,6 +188,25 @@ void SqlQueryView::setupActionsForMenu(SqlQueryItem* currentItem, const QListaddMenu(ICONS.GENERATE_QUERY, tr("Generate query for selected cells")); generateQueryMenu->addAction(actionMap[GENERATE_SELECT]); + + Db* db = getModel()->getDb(); + if (db && db->isValid()) + { + QList functions = FUNCTIONS->getScriptFunctionsForDatabase(db->getName()); + if (functions.size() > 0) + { + QStringList fnNames; + // Offer functions with undefined arguments or at least 1 defined argument + for (FunctionManager::ScriptFunction* fn : functions) + if (fn->undefinedArgs || fn->arguments.size() >= 1) + fnNames << fn->name; + fnNames.sort(); + QMenu* generateSelectFunctionMenu = generateQueryMenu->addMenu("SELECT function(...)"); + for (const QString& name : fnNames) + generateSelectFunctionMenu->addAction(name, this, SLOT(generateSelectFunction())); + } + } + if (getModel()->supportsModifyingQueriesInMenu()) { generateQueryMenu->addAction(actionMap[GENERATE_INSERT]); @@ -310,7 +331,20 @@ void SqlQueryView::generateSelect() { QString sql = getModel()->generateSelectQueryForItems(getSelectedItems()); MAINWINDOW->openSqlEditor(getModel()->getDb(), sql); +} +void SqlQueryView::generateSelectFunction() +{ + QString function = reinterpret_cast(sender())->text(); + QString sql = getModel()->generateSelectFunctionQueryForItems(function, getSelectedItems()); + MAINWINDOW->openSqlEditor(getModel()->getDb(), sql); + EditorWindow* win = MAINWINDOW->openSqlEditor(getModel()->getDb(), sql); + if (!win) + return; + + static_qstring(tpl, "%1(...)"); + win->getMdiWindow()->rename(tpl.arg(function)); + win->execute(); } void SqlQueryView::generateInsert() diff --git a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h index 200e366ab0..621d9cd58b 100644 --- a/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h +++ b/SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h @@ -144,6 +144,7 @@ class GUI_API_EXPORT SqlQueryView : public QTableView, public ExtActionContainer void updateFont(); void itemActivated(const QModelIndex& index); void generateSelect(); + void generateSelectFunction(); void generateInsert(); void generateUpdate(); void generateDelete();