Skip to content

Commit

Permalink
Merge pull request #5050 from tuffnatty/feature/select-function
Browse files Browse the repository at this point in the history
Implemented "Generate SELECT function(...)" data grid action
  • Loading branch information
pawelsalawa authored Aug 22, 2024
2 parents 0d835df + b2b2276 commit 8440db4
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 3 deletions.
42 changes: 40 additions & 2 deletions SQLiteStudio3/coreSQLiteStudio/querygenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<QString, QVariantList> 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<QString>& usedAliases)
{
static_qstring(tpl, "%2%1");
Expand Down Expand Up @@ -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<QVariantList> values)
QStringList QueryGenerator::toValueSets(const QStringList& columns, const StrHash<QVariantList> values,
const QString& format)
{
QStringList rows;
QVariantList rowValues;
Expand All @@ -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;
Expand Down
12 changes: 11 additions & 1 deletion SQLiteStudio3/coreSQLiteStudio/querygenerator.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,24 @@ class API_EXPORT QueryGenerator
*/
QString generateSelectFromSelect(Db* db, const QString& initialSelect, const StrHash<QVariantList> values = StrHash<QVariantList>(), 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<QString, QVariantList> values);

private:
QString generateSelectFromTableOrView(Db* db, const QString& database, const QString& tableOrView, const QStringList& columns, const StrHash<QVariantList> values = StrHash<QVariantList>());
QString getAlias(const QString& name, QSet<QString>& usedAliases);
QStringList valuesToConditionList(const StrHash<QVariantList>& values);
QString valuesToConditionStr(const StrHash<QVariantList>& values);
QString toResultColumnString(const SelectResolver::Column& column);
QString toFullObjectName(const QString& database, const QString& object);
QStringList toValueSets(const QStringList& columns, const StrHash<QVariantList> values);
QStringList toValueSets(const QStringList& columns, const StrHash<QVariantList> values,
const QString& format = QString());
};

#endif // QUERYGENERATOR_H
27 changes: 27 additions & 0 deletions SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,17 @@ QString SqlQueryModel::generateSelectQueryForItems(const QList<SqlQueryItem*>& i
return sql;
}

QString SqlQueryModel::generateSelectFunctionQueryForItems(const QString& function, const QList<SqlQueryItem*>& items)
{
QHash<QString, QVariantList> values = toValuesGroupedByColumns(items);
QStringList orderedColumns = toOrderedColumnNames(items);

QueryGenerator generator;
QString sql = generator.generateSelectFunction(function, orderedColumns, values);

return sql;
}

QString SqlQueryModel::generateInsertQueryForItems(const QList<SqlQueryItem*>& items)
{
UNUSED(items);
Expand Down Expand Up @@ -1178,6 +1189,22 @@ QHash<QString, QVariantList> SqlQueryModel::toValuesGroupedByColumns(const QList
return values;
}

QStringList SqlQueryModel::toOrderedColumnNames(const QList<SqlQueryItem*>& items)
{
QStringList cols;
int row = -1;
QMap<int,QList<SqlQueryItem*>> 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;
Expand Down
2 changes: 2 additions & 0 deletions SQLiteStudio3/guiSQLiteStudio/datagrid/sqlquerymodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel
*/
void setAsyncMode(bool enabled);
virtual QString generateSelectQueryForItems(const QList<SqlQueryItem*>& items);
virtual QString generateSelectFunctionQueryForItems(const QString& function, const QList<SqlQueryItem*>& items);
virtual QString generateInsertQueryForItems(const QList<SqlQueryItem*>& items);
virtual QString generateUpdateQueryForItems(const QList<SqlQueryItem*>& items);
virtual QString generateDeleteQueryForItems(const QList<SqlQueryItem*>& items);
Expand Down Expand Up @@ -310,6 +311,7 @@ class GUI_API_EXPORT SqlQueryModel : public QStandardItemModel
RowId getNewRowId(const RowId& currentRowId, const QList<SqlQueryItem*> items);
void updateRowIdForAllItems(const AliasedTable& table, const RowId& rowId, const RowId& newRowId);
QHash<QString, QVariantList> toValuesGroupedByColumns(const QList<SqlQueryItem*>& items);
QStringList toOrderedColumnNames(const QList<SqlQueryItem*>& items);
void refreshGeneratedColumns(const QList<SqlQueryItem*>& items);
void refreshGeneratedColumns(const QList<SqlQueryItem*>& items, QHash<SqlQueryItem*, QVariant>& values, const RowId& insertedRowId);

Expand Down
34 changes: 34 additions & 0 deletions SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -186,6 +188,25 @@ void SqlQueryView::setupActionsForMenu(SqlQueryItem* currentItem, const QList<Sq
{
QMenu* generateQueryMenu = contextMenu->addMenu(ICONS.GENERATE_QUERY, tr("Generate query for selected cells"));
generateQueryMenu->addAction(actionMap[GENERATE_SELECT]);

Db* db = getModel()->getDb();
if (db && db->isValid())
{
QList<FunctionManager::ScriptFunction*> 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]);
Expand Down Expand Up @@ -310,7 +331,20 @@ void SqlQueryView::generateSelect()
{
QString sql = getModel()->generateSelectQueryForItems(getSelectedItems());
MAINWINDOW->openSqlEditor(getModel()->getDb(), sql);
}

void SqlQueryView::generateSelectFunction()
{
QString function = reinterpret_cast<QAction*>(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()
Expand Down
1 change: 1 addition & 0 deletions SQLiteStudio3/guiSQLiteStudio/datagrid/sqlqueryview.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 8440db4

Please sign in to comment.