Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom display formats #1720

Merged
merged 4 commits into from
Feb 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 52 additions & 10 deletions src/ColumnDisplayFormatDialog.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
#include <QMessageBox>

#include "ColumnDisplayFormatDialog.h"
#include "ui_ColumnDisplayFormatDialog.h"
#include "sql/sqlitetypes.h"
#include "sqlitedb.h"

ColumnDisplayFormatDialog::ColumnDisplayFormatDialog(const QString& colname, QString current_format, QWidget* parent)
ColumnDisplayFormatDialog::ColumnDisplayFormatDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& tableName, const QString& colname, QString current_format, QWidget* parent)
: QDialog(parent),
ui(new Ui::ColumnDisplayFormatDialog),
column_name(colname)
column_name(colname),
pdb(db),
curTable(tableName)
{
// Create UI
ui->setupUi(this);
Expand All @@ -28,6 +33,9 @@ ColumnDisplayFormatDialog::ColumnDisplayFormatDialog(const QString& colname, QSt
ui->comboDisplayFormat->insertSeparator(ui->comboDisplayFormat->count());
ui->comboDisplayFormat->addItem(tr("Lower case"), "lower");
ui->comboDisplayFormat->addItem(tr("Upper case"), "upper");
ui->comboDisplayFormat->insertSeparator(ui->comboDisplayFormat->count());
ui->comboDisplayFormat->addItem(tr("Custom"), "custom");
mgrojo marked this conversation as resolved.
Show resolved Hide resolved

ui->labelDisplayFormat->setText(ui->labelDisplayFormat->text().arg(column_name));

formatFunctions["decimal"] = "printf('%d', " + sqlb::escapeIdentifier(column_name) + ")";
Expand All @@ -53,19 +61,14 @@ ColumnDisplayFormatDialog::ColumnDisplayFormatDialog(const QString& colname, QSt
ui->comboDisplayFormat->setCurrentIndex(0);
updateSqlCode();
} else {
QString formatName;
// When it doesn't match any predefined format, it is considered custom
QString formatName = "custom";
for(auto& formatKey : formatFunctions.keys()) {
if(current_format == formatFunctions.value(formatKey)) {
formatName = formatKey;
break;
}
}

if(formatName.isEmpty()) {
ui->comboDisplayFormat->insertSeparator(ui->comboDisplayFormat->count());
ui->comboDisplayFormat->addItem(tr("Custom"), "custom");
formatName = "custom";
}
ui->comboDisplayFormat->setCurrentIndex(ui->comboDisplayFormat->findData(formatName));
ui->editDisplayFormat->setText(current_format);
}
Expand All @@ -90,7 +93,46 @@ void ColumnDisplayFormatDialog::updateSqlCode()

if(format == "default")
ui->editDisplayFormat->setText(sqlb::escapeIdentifier(column_name));
else
else if(format != "custom")
ui->editDisplayFormat->setText(formatFunctions.value(format));
}

void ColumnDisplayFormatDialog::accept()
{
QString errorMessage;

// Accept the SQL code if it's the column name (default), it contains a function invocation applied to the column name and it can be
// executed without errors returning only one column.
// Users could still devise a way to break this, but this is considered good enough for letting them know about simple incorrect
// cases.
if(!(ui->editDisplayFormat->text() == sqlb::escapeIdentifier(column_name) ||
ui->editDisplayFormat->text().contains(QRegExp("[a-z]+[a-z_0-9]* *\\(.*" + QRegExp::escape(sqlb::escapeIdentifier(column_name)) + ".*\\)", Qt::CaseInsensitive))))
errorMessage = tr("Custom display format must contain a function call applied to %1").arg(sqlb::escapeIdentifier(column_name));
else {
// Execute a query using the display format and check that it only returns one column.
int customNumberColumns = 0;

DBBrowserDB::execCallback callback = [&customNumberColumns](int numberColumns, QStringList, QStringList) -> bool {
customNumberColumns = numberColumns;
// Return false so the query is not aborted and no error is reported.
return false;
};
if(!pdb.executeSQL(QString("SELECT %1 FROM %2 LIMIT 1").arg(ui->editDisplayFormat->text(), curTable.toString()),
false, true, callback))
errorMessage = tr("Error in custom display format. Message from database engine:\n\n%1").arg(pdb.lastError());
else if(customNumberColumns != 1)
errorMessage = tr("Custom display format must return only one column but it returned %1.").arg(customNumberColumns);

}
if(!errorMessage.isEmpty())
QMessageBox::warning(this, QApplication::applicationName(), errorMessage);
else
QDialog::accept();
}

void ColumnDisplayFormatDialog::setCustom(bool modified)
{
// If the SQL code is modified by user, select the custom value in the combo-box
if(modified && ui->editDisplayFormat->hasFocus())
ui->comboDisplayFormat->setCurrentIndex(ui->comboDisplayFormat->findData("custom"));
}
10 changes: 9 additions & 1 deletion src/ColumnDisplayFormatDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#include <QString>
#include <QMap>

#include "sql/sqlitetypes.h"

class DBBrowserDB;

namespace Ui {
class ColumnDisplayFormatDialog;
}
Expand All @@ -14,18 +18,22 @@ class ColumnDisplayFormatDialog : public QDialog
Q_OBJECT

public:
explicit ColumnDisplayFormatDialog(const QString& colname, QString current_format, QWidget* parent = nullptr);
explicit ColumnDisplayFormatDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& tableName, const QString& colname, QString current_format, QWidget* parent = nullptr);
~ColumnDisplayFormatDialog() override;

QString selectedDisplayFormat() const;

private slots:
void updateSqlCode();
void accept() override;
void setCustom(bool modified);

private:
Ui::ColumnDisplayFormatDialog* ui;
QString column_name;
QMap<QString, QString> formatFunctions;
DBBrowserDB& pdb;
sqlb::ObjectIdentifier curTable;
};

#endif
23 changes: 18 additions & 5 deletions src/ColumnDisplayFormatDialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,7 @@
<widget class="QComboBox" name="comboDisplayFormat"/>
</item>
<item>
<widget class="SqlTextEdit" name="editDisplayFormat">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
<widget class="SqlTextEdit" name="editDisplayFormat"/>
</item>
</layout>
</widget>
Expand Down Expand Up @@ -114,8 +110,25 @@
</hint>
</hints>
</connection>
<connection>
<sender>editDisplayFormat</sender>
<signal>modificationChanged(bool)</signal>
<receiver>ColumnDisplayFormatDialog</receiver>
<slot>setCustom(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>125</x>
<y>69</y>
</hint>
<hint type="destinationlabel">
<x>244</x>
<y>4</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>updateSqlCode()</slot>
<slot>setCustom()</slot>
</slots>
</ui>
2 changes: 1 addition & 1 deletion src/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3350,7 +3350,7 @@ void MainWindow::editDataColumnDisplayFormat()
QString current_displayformat = browseTableSettings[current_table].displayFormats[field_number];

// Open the dialog
ColumnDisplayFormatDialog dialog(field_name, current_displayformat, this);
ColumnDisplayFormatDialog dialog(db, current_table, field_name, current_displayformat, this);
if(dialog.exec())
{
// Set the newly selected display format
Expand Down
21 changes: 19 additions & 2 deletions src/sqlitedb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,23 @@ bool DBBrowserDB::dump(const QString& filePath,
return false;
}

bool DBBrowserDB::executeSQL(QString statement, bool dirtyDB, bool logsql)
// Callback for sqlite3_exec. It receives the user callback in the first parameter. Converts parameters
// to C++ classes and calls user callback.
int DBBrowserDB::callbackWrapper (void* callback, int numberColumns, char** values, char** columnNames)
{
QStringList valuesList;
QStringList namesList;

for (int i=0; i<numberColumns; i++) {
valuesList << QString(values[i]);
namesList << QString(columnNames[i]);
}

execCallback userCallback = *(static_cast<execCallback*>(callback));
return userCallback(numberColumns, valuesList, namesList);
}

bool DBBrowserDB::executeSQL(QString statement, bool dirtyDB, bool logsql, execCallback callback)
{
waitForDbRelease();
if(!_db)
Expand All @@ -905,7 +921,7 @@ bool DBBrowserDB::executeSQL(QString statement, bool dirtyDB, bool logsql)
if (dirtyDB) setSavepoint();

char* errmsg;
if (SQLITE_OK == sqlite3_exec(_db, statement.toUtf8(), nullptr, nullptr, &errmsg))
if (SQLITE_OK == sqlite3_exec(_db, statement.toUtf8(), callback ? callbackWrapper : nullptr, &callback, &errmsg))
{
// Update DB structure after executing an SQL statement. But try to avoid doing unnecessary updates.
if(!dontCheckForStructureUpdates && (statement.startsWith("ALTER", Qt::CaseInsensitive) ||
Expand All @@ -919,6 +935,7 @@ bool DBBrowserDB::executeSQL(QString statement, bool dirtyDB, bool logsql)
lastErrorMessage = QString("%1 (%2)").arg(QString::fromUtf8(errmsg)).arg(statement);
qWarning() << "executeSQL: " << statement << "->" << errmsg;
sqlite3_free(errmsg);

return false;
}
}
Expand Down
17 changes: 15 additions & 2 deletions src/sqlitedb.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <condition_variable>
#include <memory>
#include <mutex>
#include <functional>

#include <QByteArray>
#include <QMultiMap>
Expand Down Expand Up @@ -54,6 +55,7 @@ class DBBrowserDB : public QObject
};

public:

explicit DBBrowserDB () : _db(nullptr), db_used(false), isEncrypted(false), isReadOnly(false), dontCheckForStructureUpdates(false) {}
~DBBrowserDB () override {}

Expand Down Expand Up @@ -104,8 +106,17 @@ class DBBrowserDB : public QObject
Wait,
CancelOther
};

bool executeSQL(QString statement, bool dirtyDB = true, bool logsql = true);
// Callback to get results from executeSQL(). It is invoked for
// each result row coming out of the evaluated SQL statements. If
// a callback returns true (abort), the executeSQL() method
// returns false (error) without invoking the callback again and
// without running any subsequent SQL statements. The 1st argument
// is the number of columns in the result. The 2nd argument to the
// callback is the text representation of the values, one for each
// column. The 3rd argument is a list of strings where each entry
// represents the name of corresponding result column.
typedef std::function<bool(int, QStringList, QStringList)> execCallback;
bool executeSQL(QString statement, bool dirtyDB = true, bool logsql = true, execCallback callback = nullptr);
bool executeMultiSQL(QByteArray query, bool dirty = true, bool log = false);
QByteArray querySingleValueFromDb(const QString& sql, bool log = true, ChoiceOnUse choice = Ask);

Expand Down Expand Up @@ -136,6 +147,8 @@ class DBBrowserDB : public QObject
*/
QString max(const sqlb::ObjectIdentifier& tableName, const sqlb::Field& field) const;

static int callbackWrapper (void* callback, int numberColumns, char** values, char** columnNames);

public:
void updateSchema();

Expand Down