Skip to content

Commit

Permalink
Add a history provider for executed SQL commands
Browse files Browse the repository at this point in the history
This history provider stores all executed database SQL commands
in the local history db (ie commands executed through the browser
panel, "Execute SQL" context menu action)

The connection details, runtime and affected rows are stored
  • Loading branch information
nyalldawson committed Apr 26, 2023
1 parent c1045a2 commit 6ddae04
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 2 deletions.
10 changes: 10 additions & 0 deletions src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -61,6 +61,8 @@
#include "qgsrelationshipsitem.h"
#include "qgsprovidersqlquerybuilder.h"
#include "qgsdbrelationshipwidget.h"
#include "qgsdbqueryhistoryprovider.h"
#include "qgshistoryproviderregistry.h"

#include <QFileInfo>
#include <QMenu>
Expand Down Expand Up @@ -1642,6 +1644,14 @@ QWidget *QgsFieldItemGuiProvider::createParamWidget( QgsDataItem *item, QgsDataI
return nullptr;
}

QgsDatabaseItemGuiProvider::QgsDatabaseItemGuiProvider()
{
if ( QgsDatabaseQueryHistoryProvider *historyProvider = qobject_cast< QgsDatabaseQueryHistoryProvider * >( QgsGui::historyProviderRegistry()->providerById( QStringLiteral( "dbquery" ) ) ) )
{
connect( historyProvider, &QgsDatabaseQueryHistoryProvider::openSqlDialog, this, &QgsDatabaseItemGuiProvider::openSqlDialogGeneric );
}
}

QString QgsDatabaseItemGuiProvider::name()
{
return QStringLiteral( "database" );
Expand Down
2 changes: 1 addition & 1 deletion src/app/browser/qgsinbuiltdataitemproviders.h
Expand Up @@ -282,7 +282,7 @@ class QgsDatabaseItemGuiProvider : public QObject, public QgsDataItemGuiProvider

public:

QgsDatabaseItemGuiProvider() = default;
QgsDatabaseItemGuiProvider();

QString name() override;

Expand Down
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -507,6 +507,7 @@ set(QGIS_GUI_SRCS
qgsdataitemguiproviderregistry.cpp
qgsdatumtransformdialog.cpp
qgsdatasourceselectdialog.cpp
qgsdbqueryhistoryprovider.cpp
qgsdbrelationshipwidget.cpp
qgsdetaileditemdata.cpp
qgsdetaileditemdelegate.cpp
Expand Down Expand Up @@ -775,6 +776,7 @@ set(QGIS_GUI_HDRS
qgsdataitemguiproviderregistry.h
qgsdatasourcemanagerdialog.h
qgsdatasourceselectdialog.h
qgsdbqueryhistoryprovider.h
qgsdbrelationshipwidget.h
qgsnewdatabasetablenamewidget.h
qgsdatumtransformdialog.h
Expand Down
2 changes: 2 additions & 0 deletions src/gui/history/qgshistoryproviderregistry.cpp
Expand Up @@ -22,6 +22,7 @@
#include "qgsxmlutils.h"
#include "qgsprocessinghistoryprovider.h"
#include "qgshistoryentry.h"
#include "qgsdbqueryhistoryprovider.h"

#include <QFile>
#include <sqlite3.h>
Expand Down Expand Up @@ -59,6 +60,7 @@ QgsHistoryProviderRegistry::~QgsHistoryProviderRegistry()
void QgsHistoryProviderRegistry::addDefaultProviders()
{
addProvider( new QgsProcessingHistoryProvider() );
addProvider( new QgsDatabaseQueryHistoryProvider() );
}

bool QgsHistoryProviderRegistry::addProvider( QgsAbstractHistoryProvider *provider )
Expand Down
203 changes: 203 additions & 0 deletions src/gui/qgsdbqueryhistoryprovider.cpp
@@ -0,0 +1,203 @@
/***************************************************************************
qgsdbqueryhistoryprovider.cpp
--------------------------
begin : April 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsdbqueryhistoryprovider.h"
#include "qgscodeeditorsql.h"
#include "qgshistoryentry.h"
#include "qgsprovidermetadata.h"
#include "qgsproviderregistry.h"

#include <QIcon>

class QgsDatabaseQueryValueNode;

///@cond PRIVATE
class DatabaseQueryHistoryNode : public QgsHistoryEntryGroup
{
public:

DatabaseQueryHistoryNode( const QgsHistoryEntry &entry, QgsDatabaseQueryHistoryProvider *provider )
: QgsHistoryEntryGroup()
, mEntry( entry )
, mProvider( provider )
{
}

protected:

QgsHistoryEntry mEntry;
QgsDatabaseQueryValueNode *mConnectionNode = nullptr;
QgsDatabaseQueryValueNode *mRowsNode = nullptr;
QgsDatabaseQueryValueNode *mTimeNode = nullptr;
QgsDatabaseQueryHistoryProvider *mProvider = nullptr;

};

class DatabaseQueryValueNode : public DatabaseQueryHistoryNode
{
public:

DatabaseQueryValueNode( const QgsHistoryEntry &entry, QgsDatabaseQueryHistoryProvider *provider, const QString &value )
: DatabaseQueryHistoryNode( entry, provider )
, mValue( value )
{}

QVariant data( int role = Qt::DisplayRole ) const override
{
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
return mValue;

default:
return QVariant();
}
}

QString html( const QgsHistoryWidgetContext & ) const override
{
return mValue;
}

private:

QString mValue;

};

class DatabaseQueryRootNode : public DatabaseQueryHistoryNode
{
public:

DatabaseQueryRootNode( const QgsHistoryEntry &entry, QgsDatabaseQueryHistoryProvider *provider )
: DatabaseQueryHistoryNode( entry, provider )
{
setEntry( entry );

mProviderKey = mEntry.entry.value( QStringLiteral( "provider" ) ).toString();
}

QVariant data( int role = Qt::DisplayRole ) const override
{
switch ( role )
{
case Qt::DisplayRole:
case Qt::ToolTipRole:
return mEntry.entry.value( QStringLiteral( "query" ) );

case Qt::DecorationRole:
{
if ( !mProviderIcon.isNull() )
return mProviderIcon;

if ( QgsProviderMetadata *md = QgsProviderRegistry::instance()->providerMetadata( mProviderKey ) )
{
mProviderIcon = md->icon();
}
return mProviderIcon;
}

default:
break;
}
return QVariant();
}

void setEntry( const QgsHistoryEntry &entry )
{
if ( !mConnectionNode )
{
mConnectionNode = new DatabaseQueryValueNode( mEntry, mProvider, QStringLiteral( "Connection: %1" ).arg( entry.entry.value( QStringLiteral( "connection" ) ).toString() ) );
addChild( mConnectionNode );
}
if ( entry.entry.contains( QStringLiteral( "rows" ) ) )
{
if ( !mRowsNode )
{
mRowsNode = new DatabaseQueryValueNode( mEntry, mProvider, QStringLiteral( "Row count: %1" ).arg( entry.entry.value( QStringLiteral( "rows" ) ).toString() ) );
addChild( mRowsNode );
}
}
if ( entry.entry.contains( QStringLiteral( "time" ) ) )
{
if ( !mTimeNode )
{
mTimeNode = new DatabaseQueryValueNode( mEntry, mProvider, QStringLiteral( "Execution time: %1 ms" ).arg( entry.entry.value( QStringLiteral( "time" ) ).toString() ) );
addChild( mTimeNode );
}
}
}

QWidget *createWidget( const QgsHistoryWidgetContext & ) override
{
QgsCodeEditorSQL *editor = new QgsCodeEditorSQL();
editor->setText( mEntry.entry.value( QStringLiteral( "query" ) ).toString() );
editor->setReadOnly( true );
editor->setCaretLineVisible( false );
editor->setLineNumbersVisible( false );
editor->setFoldingVisible( false );
editor->setEdgeMode( QsciScintilla::EdgeNone );
editor->setWrapMode( QsciScintilla::WrapMode::WrapWord );
return editor;
}

void doubleClicked( const QgsHistoryWidgetContext & ) override
{
mProvider->emitOpenSqlDialog( mEntry.entry.value( QStringLiteral( "connection" ) ).toString(),
mEntry.entry.value( QStringLiteral( "provider" ) ).toString(),
mEntry.entry.value( QStringLiteral( "query" ) ).toString() );
}

private:

QString mProviderKey;
mutable QIcon mProviderIcon;
DatabaseQueryValueNode *mConnectionNode = nullptr;
DatabaseQueryValueNode *mRowsNode = nullptr;
DatabaseQueryValueNode *mTimeNode = nullptr;

};

///@endcond


QgsDatabaseQueryHistoryProvider::QgsDatabaseQueryHistoryProvider()
{
}

QString QgsDatabaseQueryHistoryProvider::id() const
{
return QStringLiteral( "dbquery" );
}

QgsHistoryEntryNode *QgsDatabaseQueryHistoryProvider::createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & )
{
return new DatabaseQueryRootNode( entry, this );
}

void QgsDatabaseQueryHistoryProvider::updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext & )
{
if ( DatabaseQueryRootNode *dbNode = dynamic_cast< DatabaseQueryRootNode * >( node ) )
{
dbNode->setEntry( entry );
}
}

void QgsDatabaseQueryHistoryProvider::emitOpenSqlDialog( const QString &connectionUri, const QString &provider, const QString &sql )
{
emit openSqlDialog( connectionUri, provider, sql );
}
59 changes: 59 additions & 0 deletions src/gui/qgsdbqueryhistoryprovider.h
@@ -0,0 +1,59 @@
/***************************************************************************
qgsdbqueryhistoryprovider.h
--------------------------
begin : April 2023
copyright : (C) 2023 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSDBQUERYHISTORYPROVIDER_H
#define QGSDBQUERYHISTORYPROVIDER_H

#include "qgis_gui.h"
#include "qgis_sip.h"

#include "qgshistoryprovider.h"
#include "qgshistoryentrynode.h"

#define SIP_SKIP

/**
* History provider for operations database queries.
*
* \ingroup gui
*
* \note Not available in Python bindings
* \since QGIS 3.32
*/
class GUI_EXPORT QgsDatabaseQueryHistoryProvider : public QgsAbstractHistoryProvider
{
Q_OBJECT

public:

QgsDatabaseQueryHistoryProvider();

QString id() const override;

QgsHistoryEntryNode *createNodeForEntry( const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override SIP_FACTORY;
void updateNodeForEntry( QgsHistoryEntryNode *node, const QgsHistoryEntry &entry, const QgsHistoryWidgetContext &context ) override;

void emitOpenSqlDialog( const QString &connectionUri, const QString &provider, const QString &sql );

signals:

void openSqlDialog( const QString &connectionUri, const QString &provider, const QString &sql );

};

#endif //QGSDBQUERYHISTORYPROVIDER_H



22 changes: 21 additions & 1 deletion src/gui/qgsqueryresultwidget.cpp
Expand Up @@ -21,6 +21,9 @@
#include "qgsquerybuilder.h"
#include "qgsvectorlayer.h"
#include "qgsapplication.h"
#include "qgsgui.h"
#include "qgshistoryproviderregistry.h"
#include "qgshistoryentry.h"

#include <QClipboard>
#include <QShortcut>
Expand Down Expand Up @@ -165,11 +168,20 @@ void QgsQueryResultWidget::executeQuery()
mFirstRowFetched = false;

cancelRunningQuery();

if ( mConnection )
{
const QString sql { mSqlEditor->text( ) };

bool ok = false;
mCurrentHistoryEntryId = QgsGui::historyProviderRegistry()->addEntry( QStringLiteral( "dbquery" ),
QVariantMap
{
{ QStringLiteral( "query" ), sql },
{ QStringLiteral( "provider" ), mConnection->providerKey() },
{ QStringLiteral( "connection" ), mConnection->uri() },
},
ok );

mWasCanceled = false;
mFeedback = std::make_unique<QgsFeedback>();
mStopButton->setEnabled( true );
Expand Down Expand Up @@ -390,6 +402,14 @@ void QgsQueryResultWidget::startFetching()

connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [ = ]
{
bool ok = false;
const QgsHistoryEntry currentHistoryEntry = QgsGui::historyProviderRegistry()->entry( mCurrentHistoryEntryId, ok );
QVariantMap entryDetails = currentHistoryEntry.entry;
entryDetails.insert( QStringLiteral( "rows" ), mActualRowCount );
entryDetails.insert( QStringLiteral( "time" ), mQueryResultWatcher.result().queryExecutionTime() );

QgsGui::historyProviderRegistry()->updateEntry( mCurrentHistoryEntryId,
entryDetails );
mProgressBar->hide();
mStopButton->setEnabled( false );
} );
Expand Down
1 change: 1 addition & 0 deletions src/gui/qgsqueryresultwidget.h
Expand Up @@ -226,6 +226,7 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
long long mActualRowCount = -1;
long long mFetchedRowsBatchCount = 0;
QueryWidgetMode mQueryWidgetMode = QueryWidgetMode::SqlQueryMode;
long long mCurrentHistoryEntryId = -1;

/**
* Updates SQL layer columns.
Expand Down

0 comments on commit 6ddae04

Please sign in to comment.