mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-06 00:07:29 -04:00
Add query log
This commit is contained in:
parent
4c88bc54b0
commit
0108041c68
@ -854,6 +854,13 @@ Returns the application's bookmark manager, used for storing installation-wide b
|
||||
Returns the handler for recently used style items.
|
||||
|
||||
.. versionadded:: 3.22
|
||||
%End
|
||||
|
||||
static QgsDatabaseQueryLog *databaseQueryLog() /KeepReference/;
|
||||
%Docstring
|
||||
Returns the database query log.
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
|
||||
static QgsStyleModel *defaultStyleModel();
|
||||
|
102
python/core/auto_generated/qgsdbquerylog.sip.in
Normal file
102
python/core/auto_generated/qgsdbquerylog.sip.in
Normal file
@ -0,0 +1,102 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsdbquerylog.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
class QgsDatabaseQueryLogEntry
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Encapsulates a logged database query.
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsdbquerylog.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsDatabaseQueryLogEntry( const QString &query = QString() );
|
||||
%Docstring
|
||||
Constructor for QgsDatabaseQueryLogEntry.
|
||||
%End
|
||||
|
||||
QString query;
|
||||
|
||||
QString initiatorClass;
|
||||
|
||||
QString origin;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class QgsDatabaseQueryLog: QObject
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
Handles logging of database queries.
|
||||
|
||||
:py:class:`QgsDatabaseQueryLog` is not usually directly created, but rather accessed through
|
||||
:py:func:`QgsApplication.databaseQueryLog()`. Generally, clients should only access the
|
||||
static :py:func:`~QgsDatabaseQueryLogEntry.log` method to register their queries.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Log a database query
|
||||
QgsDatabaseQueryLogger.log('SELECT * FROM my_table')
|
||||
|
||||
.. versionadded:: 3.24
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsdbquerylog.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsDatabaseQueryLog( QObject *parent = 0 );
|
||||
%Docstring
|
||||
Creates a new query logger.
|
||||
|
||||
:py:class:`QgsDatabaseQueryLogger` is not usually directly created, but rather accessed through
|
||||
:py:func:`QgsApplication.queryLogger()`.
|
||||
%End
|
||||
|
||||
static void log( const QString &query );
|
||||
%Docstring
|
||||
Logs a database query.
|
||||
|
||||
Consider using the variant with a :py:class:`QgsDatabaseQueryLogEntry` argument instead, as that
|
||||
method allows more context for logged queries.
|
||||
|
||||
This method can be safely called from any thread.
|
||||
%End
|
||||
|
||||
static void log( const QgsDatabaseQueryLogEntry &entry );
|
||||
%Docstring
|
||||
Logs a database query ``entry``.
|
||||
|
||||
This method can be safely called from any thread.
|
||||
%End
|
||||
|
||||
public slots:
|
||||
|
||||
|
||||
signals:
|
||||
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsdbquerylog.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -38,6 +38,7 @@
|
||||
%Include auto_generated/qgsdatasourceuri.sip
|
||||
%Include auto_generated/qgsdatetimestatisticalsummary.sip
|
||||
%Include auto_generated/qgsdbfilterproxymodel.sip
|
||||
%Include auto_generated/qgsdbquerylog.sip
|
||||
%Include auto_generated/qgsdefaultvalue.sip
|
||||
%Include auto_generated/qgsdiagramrenderer.sip
|
||||
%Include auto_generated/qgsdistancearea.sip
|
||||
|
@ -169,6 +169,10 @@ set(QGIS_APP_SRCS
|
||||
devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp
|
||||
devtools/profiler/qgsprofilerpanelwidget.cpp
|
||||
devtools/profiler/qgsprofilerwidgetfactory.cpp
|
||||
devtools/querylogger/qgsappquerylogger.cpp
|
||||
devtools/querylogger/qgsqueryloggernode.cpp
|
||||
devtools/querylogger/qgsqueryloggerpanelwidget.cpp
|
||||
devtools/querylogger/qgsqueryloggerwidgetfactory.cpp
|
||||
|
||||
dwg/qgsdwgimportdialog.cpp
|
||||
dwg/qgsdwgimporter.cpp
|
||||
|
244
src/app/devtools/querylogger/qgsappquerylogger.cpp
Normal file
244
src/app/devtools/querylogger/qgsappquerylogger.cpp
Normal file
@ -0,0 +1,244 @@
|
||||
/***************************************************************************
|
||||
qgsappquerylogger.cpp
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 "qgsappquerylogger.h"
|
||||
#include "qgsqueryloggernode.h"
|
||||
#include "qgsapplication.h"
|
||||
#include "qgssettings.h"
|
||||
#include "qgis.h"
|
||||
#include <QThread>
|
||||
#include <QApplication>
|
||||
#include <QUrlQuery>
|
||||
|
||||
QgsAppQueryLogger::QgsAppQueryLogger( QObject *parent )
|
||||
: QAbstractItemModel( parent )
|
||||
, mRootNode( std::make_unique< QgsDatabaseQueryLoggerRootNode >() )
|
||||
{
|
||||
// logger must be created on the main thread
|
||||
Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
|
||||
|
||||
if ( QgsSettings().value( QStringLiteral( "logQueries" ), false, QgsSettings::App ).toBool() )
|
||||
enableLogging( true );
|
||||
}
|
||||
|
||||
bool QgsAppQueryLogger::isLogging() const
|
||||
{
|
||||
return mIsLogging;
|
||||
}
|
||||
|
||||
QgsAppQueryLogger::~QgsAppQueryLogger() = default;
|
||||
|
||||
void QgsAppQueryLogger::enableLogging( bool enabled )
|
||||
{
|
||||
if ( enabled )
|
||||
{
|
||||
connect( QgsApplication::databaseQueryLog(), &QgsDatabaseQueryLog::queryStarted, this, &QgsAppQueryLogger::queryLogged, Qt::UniqueConnection );
|
||||
}
|
||||
else
|
||||
{
|
||||
disconnect( QgsApplication::databaseQueryLog(), &QgsDatabaseQueryLog::queryStarted, this, &QgsAppQueryLogger::queryLogged );
|
||||
}
|
||||
mIsLogging = enabled;
|
||||
}
|
||||
|
||||
void QgsAppQueryLogger::clear()
|
||||
{
|
||||
beginResetModel();
|
||||
mRequestGroups.clear();
|
||||
mRootNode->clear();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void QgsAppQueryLogger::queryLogged( const QgsDatabaseQueryLogEntry &query )
|
||||
{
|
||||
const int childCount = mRootNode->childCount();
|
||||
|
||||
beginInsertRows( QModelIndex(), childCount, childCount );
|
||||
|
||||
std::unique_ptr< QgsDatabaseQueryLoggerGroup > group = std::make_unique< QgsDatabaseQueryLoggerGroup >( query.query );
|
||||
// mRequestGroups.insert( parameters.requestId(), group.get() );
|
||||
mRootNode->addChild( std::move( group ) );
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QgsDatabaseQueryLoggerNode *QgsAppQueryLogger::index2node( const QModelIndex &index ) const
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
return mRootNode.get();
|
||||
|
||||
return reinterpret_cast<QgsDatabaseQueryLoggerNode *>( index.internalPointer() );
|
||||
}
|
||||
|
||||
QList<QAction *> QgsAppQueryLogger::actions( const QModelIndex &index, QObject *parent )
|
||||
{
|
||||
QgsDatabaseQueryLoggerNode *node = index2node( index );
|
||||
if ( !node )
|
||||
return QList< QAction * >();
|
||||
|
||||
return node->actions( parent );
|
||||
}
|
||||
|
||||
QModelIndex QgsAppQueryLogger::node2index( QgsDatabaseQueryLoggerNode *node ) const
|
||||
{
|
||||
if ( !node || !node->parent() )
|
||||
return QModelIndex(); // this is the only root item -> invalid index
|
||||
|
||||
QModelIndex parentIndex = node2index( node->parent() );
|
||||
|
||||
int row = node->parent()->indexOf( node );
|
||||
Q_ASSERT( row >= 0 );
|
||||
return index( row, 0, parentIndex );
|
||||
}
|
||||
|
||||
QModelIndex QgsAppQueryLogger::indexOfParentLayerTreeNode( QgsDatabaseQueryLoggerNode *parentNode ) const
|
||||
{
|
||||
Q_ASSERT( parentNode );
|
||||
|
||||
QgsDatabaseQueryLoggerGroup *grandParentNode = parentNode->parent();
|
||||
if ( !grandParentNode )
|
||||
return QModelIndex(); // root node -> invalid index
|
||||
|
||||
int row = grandParentNode->indexOf( parentNode );
|
||||
Q_ASSERT( row >= 0 );
|
||||
|
||||
return createIndex( row, 0, parentNode );
|
||||
}
|
||||
|
||||
void QgsAppQueryLogger::removeRequestRows( const QList<int> &rows )
|
||||
{
|
||||
QList< int > res = rows;
|
||||
std::sort( res.begin(), res.end(), std::greater< int >() );
|
||||
|
||||
for ( int row : std::as_const( res ) )
|
||||
{
|
||||
int popId = data( index( row, 0, QModelIndex() ), QgsDatabaseQueryLoggerNode::RoleId ).toInt();
|
||||
mRequestGroups.remove( popId );
|
||||
|
||||
beginRemoveRows( QModelIndex(), row, row );
|
||||
mRootNode->removeRow( row );
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
QgsDatabaseQueryLoggerRootNode *QgsAppQueryLogger::rootGroup()
|
||||
{
|
||||
return mRootNode.get();
|
||||
}
|
||||
|
||||
int QgsAppQueryLogger::rowCount( const QModelIndex &parent ) const
|
||||
{
|
||||
QgsDatabaseQueryLoggerNode *n = index2node( parent );
|
||||
if ( !n )
|
||||
return 0;
|
||||
|
||||
return n->childCount();
|
||||
}
|
||||
|
||||
int QgsAppQueryLogger::columnCount( const QModelIndex &parent ) const
|
||||
{
|
||||
Q_UNUSED( parent )
|
||||
return 1;
|
||||
}
|
||||
|
||||
QModelIndex QgsAppQueryLogger::index( int row, int column, const QModelIndex &parent ) const
|
||||
{
|
||||
if ( column < 0 || column >= columnCount( parent ) ||
|
||||
row < 0 || row >= rowCount( parent ) )
|
||||
return QModelIndex();
|
||||
|
||||
QgsDatabaseQueryLoggerGroup *n = dynamic_cast< QgsDatabaseQueryLoggerGroup * >( index2node( parent ) );
|
||||
if ( !n )
|
||||
return QModelIndex(); // have no children
|
||||
|
||||
return createIndex( row, column, n->childAt( row ) );
|
||||
}
|
||||
|
||||
QModelIndex QgsAppQueryLogger::parent( const QModelIndex &child ) const
|
||||
{
|
||||
if ( !child.isValid() )
|
||||
return QModelIndex();
|
||||
|
||||
if ( QgsDatabaseQueryLoggerNode *n = index2node( child ) )
|
||||
{
|
||||
return indexOfParentLayerTreeNode( n->parent() ); // must not be null
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_ASSERT( false );
|
||||
return QModelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant QgsAppQueryLogger::data( const QModelIndex &index, int role ) const
|
||||
{
|
||||
if ( !index.isValid() || index.column() > 1 )
|
||||
return QVariant();
|
||||
|
||||
QgsDatabaseQueryLoggerNode *node = index2node( index );
|
||||
if ( !node )
|
||||
return QVariant();
|
||||
|
||||
return node->data( role );
|
||||
}
|
||||
|
||||
Qt::ItemFlags QgsAppQueryLogger::flags( const QModelIndex &index ) const
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
Qt::ItemFlags rootFlags = Qt::ItemFlags();
|
||||
return rootFlags;
|
||||
}
|
||||
|
||||
Qt::ItemFlags f = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
return f;
|
||||
}
|
||||
|
||||
QVariant QgsAppQueryLogger::headerData( int section, Qt::Orientation orientation, int role ) const
|
||||
{
|
||||
if ( section == 0 && orientation == Qt::Horizontal && role == Qt::DisplayRole )
|
||||
return tr( "Requests" );
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerProxyModel
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerProxyModel::QgsDatabaseQueryLoggerProxyModel( QgsAppQueryLogger *logger, QObject *parent )
|
||||
: QSortFilterProxyModel( parent )
|
||||
, mLogger( logger )
|
||||
{
|
||||
setSourceModel( mLogger );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerProxyModel::setFilterString( const QString &string )
|
||||
{
|
||||
mFilterString = string;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool QgsDatabaseQueryLoggerProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const
|
||||
{
|
||||
QgsDatabaseQueryLoggerNode *node = mLogger->index2node( mLogger->index( source_row, 0, source_parent ) );
|
||||
#if 0
|
||||
if ( QgsDatabaseQueryLoggerRequestGroup *request = dynamic_cast< QgsDatabaseQueryLoggerRequestGroup * >( node ) )
|
||||
{
|
||||
return mFilterString.isEmpty() || request->url().url().contains( mFilterString, Qt::CaseInsensitive );
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
147
src/app/devtools/querylogger/qgsappquerylogger.h
Normal file
147
src/app/devtools/querylogger/qgsappquerylogger.h
Normal file
@ -0,0 +1,147 @@
|
||||
/***************************************************************************
|
||||
qgsappquerylogger.h
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 QGSAPPQUERYLOGGER_H
|
||||
#define QGSAPPQUERYLOGGER_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QElapsedTimer>
|
||||
#include "qgsdbquerylog.h"
|
||||
#include <memory>
|
||||
|
||||
class QgsDatabaseQueryLoggerNode;
|
||||
class QgsDatabaseQueryLoggerRequestGroup;
|
||||
class QgsDatabaseQueryLoggerRootNode;
|
||||
class QAction;
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsAppQueryLogger
|
||||
* \brief Logs sql queries, converting them
|
||||
* to a QAbstractItemModel representing the request and response details.
|
||||
*/
|
||||
class QgsAppQueryLogger : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsAppQueryLogger, logging requests from the specified \a manager.
|
||||
*
|
||||
* \warning QgsAppQueryLogger must be created on the main thread.
|
||||
*/
|
||||
QgsAppQueryLogger( QObject *parent );
|
||||
~QgsAppQueryLogger() override;
|
||||
|
||||
/**
|
||||
* Returns TRUE if the logger is currently logging activity.
|
||||
*/
|
||||
bool isLogging() const;
|
||||
|
||||
// Implementation of virtual functions from QAbstractItemModel
|
||||
|
||||
int rowCount( const QModelIndex &parent = QModelIndex() ) const override;
|
||||
int columnCount( const QModelIndex &parent = QModelIndex() ) const override;
|
||||
QModelIndex index( int row, int column, const QModelIndex &parent = QModelIndex() ) const override;
|
||||
QModelIndex parent( const QModelIndex &child ) const override;
|
||||
QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override;
|
||||
Qt::ItemFlags flags( const QModelIndex &index ) const override;
|
||||
QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override;
|
||||
|
||||
/**
|
||||
* Returns node for given index. Returns root node for invalid index.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerNode *index2node( const QModelIndex &index ) const;
|
||||
|
||||
/**
|
||||
* Returns a list of actions corresponding to the item at the specified \a index.
|
||||
*
|
||||
* The actions should be parented to \a parent.
|
||||
*/
|
||||
QList< QAction * > actions( const QModelIndex &index, QObject *parent );
|
||||
|
||||
/**
|
||||
* Removes a list of request \a rows from the log.
|
||||
*/
|
||||
void removeRequestRows( const QList< int > &rows );
|
||||
|
||||
/**
|
||||
* Returns the root node of the log.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerRootNode *rootGroup();
|
||||
|
||||
static constexpr int MAX_LOGGED_REQUESTS = 1000;
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* Enables or disables logging, depending on the value of \a enabled.
|
||||
*/
|
||||
void enableLogging( bool enabled );
|
||||
|
||||
/**
|
||||
* Clears all logged entries.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
private slots:
|
||||
void queryLogged( const QgsDatabaseQueryLogEntry &query );
|
||||
|
||||
private:
|
||||
|
||||
//! Returns index for a given node
|
||||
QModelIndex node2index( QgsDatabaseQueryLoggerNode *node ) const;
|
||||
QModelIndex indexOfParentLayerTreeNode( QgsDatabaseQueryLoggerNode *parentNode ) const;
|
||||
|
||||
bool mIsLogging = false;
|
||||
|
||||
std::unique_ptr< QgsDatabaseQueryLoggerRootNode > mRootNode;
|
||||
|
||||
QHash< int, QgsDatabaseQueryLoggerRequestGroup * > mRequestGroups;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerProxyModel
|
||||
* \brief A proxy model for filtering QgsNetworkLogger models by url string subsets
|
||||
* or request status.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsDatabaseQueryLoggerProxyModel, filtering the specified network \a logger.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerProxyModel( QgsAppQueryLogger *logger, QObject *parent );
|
||||
|
||||
/**
|
||||
* Sets a filter \a string to apply to request queries.
|
||||
*/
|
||||
void setFilterString( const QString &string );
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const override;
|
||||
|
||||
private:
|
||||
|
||||
QgsAppQueryLogger *mLogger = nullptr;
|
||||
|
||||
QString mFilterString;
|
||||
};
|
||||
|
||||
#endif // QGSAPPQUERYLOGGER_H
|
201
src/app/devtools/querylogger/qgsqueryloggernode.cpp
Normal file
201
src/app/devtools/querylogger/qgsqueryloggernode.cpp
Normal file
@ -0,0 +1,201 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggernode.cpp
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 "qgsqueryloggernode.h"
|
||||
#include "qgis.h"
|
||||
#include "qgsjsonutils.h"
|
||||
#include <QUrlQuery>
|
||||
#include <QColor>
|
||||
#include <QBrush>
|
||||
#include <QFont>
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerNode
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerNode::QgsDatabaseQueryLoggerNode() = default;
|
||||
QgsDatabaseQueryLoggerNode::~QgsDatabaseQueryLoggerNode() = default;
|
||||
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerGroup
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerGroup::QgsDatabaseQueryLoggerGroup( const QString &title )
|
||||
: mGroupTitle( title )
|
||||
{
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerGroup::addChild( std::unique_ptr<QgsDatabaseQueryLoggerNode> child )
|
||||
{
|
||||
if ( !child )
|
||||
return;
|
||||
|
||||
Q_ASSERT( !child->mParent );
|
||||
child->mParent = this;
|
||||
|
||||
mChildren.emplace_back( std::move( child ) );
|
||||
}
|
||||
|
||||
int QgsDatabaseQueryLoggerGroup::indexOf( QgsDatabaseQueryLoggerNode *child ) const
|
||||
{
|
||||
Q_ASSERT( child->mParent == this );
|
||||
auto it = std::find_if( mChildren.begin(), mChildren.end(), [&]( const std::unique_ptr<QgsDatabaseQueryLoggerNode> &p )
|
||||
{
|
||||
return p.get() == child;
|
||||
} );
|
||||
if ( it != mChildren.end() )
|
||||
return std::distance( mChildren.begin(), it );
|
||||
return -1;
|
||||
}
|
||||
|
||||
QgsDatabaseQueryLoggerNode *QgsDatabaseQueryLoggerGroup::childAt( int index )
|
||||
{
|
||||
Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() );
|
||||
return mChildren[ index ].get();
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerGroup::clear()
|
||||
{
|
||||
mChildren.clear();
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerGroup::data( int role ) const
|
||||
{
|
||||
switch ( role )
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
return mGroupTitle;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerGroup::toVariant() const
|
||||
{
|
||||
QVariantMap res;
|
||||
for ( const std::unique_ptr< QgsDatabaseQueryLoggerNode > &child : mChildren )
|
||||
{
|
||||
if ( const QgsDatabaseQueryLoggerValueNode *valueNode = dynamic_cast< const QgsDatabaseQueryLoggerValueNode *>( child.get() ) )
|
||||
{
|
||||
res.insert( valueNode->key(), valueNode->value() );
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerRootNode
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerRootNode::QgsDatabaseQueryLoggerRootNode()
|
||||
: QgsDatabaseQueryLoggerGroup( QString() )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerRootNode::data( int ) const
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerRootNode::removeRow( int row )
|
||||
{
|
||||
mChildren.erase( mChildren.begin() + row );
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerRootNode::toVariant() const
|
||||
{
|
||||
QVariantList res;
|
||||
for ( const std::unique_ptr< QgsDatabaseQueryLoggerNode > &child : mChildren )
|
||||
res << child->toVariant();
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerValueNode
|
||||
//
|
||||
QgsDatabaseQueryLoggerValueNode::QgsDatabaseQueryLoggerValueNode( const QString &key, const QString &value, const QColor &color )
|
||||
: mKey( key )
|
||||
, mValue( value )
|
||||
, mColor( color )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerValueNode::data( int role ) const
|
||||
{
|
||||
switch ( role )
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
return QStringLiteral( "%1: %2" ).arg( mKey.leftJustified( 30, ' ' ), mValue );
|
||||
}
|
||||
|
||||
case Qt::ForegroundRole:
|
||||
{
|
||||
if ( mColor.isValid() )
|
||||
return QBrush( mColor );
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QList<QAction *> QgsDatabaseQueryLoggerValueNode::actions( QObject *parent )
|
||||
{
|
||||
QList< QAction * > res;
|
||||
|
||||
QAction *copyAction = new QAction( QObject::tr( "Copy" ), parent );
|
||||
QObject::connect( copyAction, &QAction::triggered, copyAction, [ = ]
|
||||
{
|
||||
QApplication::clipboard()->setText( QStringLiteral( "%1: %2" ).arg( mKey, mValue ) );
|
||||
} );
|
||||
|
||||
res << copyAction;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerGroup
|
||||
//
|
||||
|
||||
void QgsDatabaseQueryLoggerGroup::addKeyValueNode( const QString &key, const QString &value, const QColor &color )
|
||||
{
|
||||
addChild( std::make_unique< QgsDatabaseQueryLoggerValueNode >( key, value, color ) );
|
||||
}
|
||||
|
||||
|
||||
QList<QAction *> QgsDatabaseQueryLoggerNode::actions( QObject * )
|
||||
{
|
||||
return QList< QAction * >();
|
||||
}
|
||||
|
||||
QVariant QgsDatabaseQueryLoggerNode::toVariant() const
|
||||
{
|
||||
return QVariant();
|
||||
}
|
197
src/app/devtools/querylogger/qgsqueryloggernode.h
Normal file
197
src/app/devtools/querylogger/qgsqueryloggernode.h
Normal file
@ -0,0 +1,197 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggernode.h
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 QgsDatabaseQueryLoggerNODE_H
|
||||
#define QgsDatabaseQueryLoggerNODE_H
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QVariant>
|
||||
#include <QColor>
|
||||
#include <QUrl>
|
||||
#include <memory>
|
||||
#include <deque>
|
||||
|
||||
class QAction;
|
||||
class QgsDatabaseQueryLoggerGroup;
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerNode
|
||||
* \brief Base class for nodes in the query logger model.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerNode
|
||||
{
|
||||
public:
|
||||
|
||||
//! Custom node data roles
|
||||
enum Roles
|
||||
{
|
||||
RoleStatus = Qt::UserRole + 1, //!< Request status role
|
||||
RoleId, //!< Request ID role
|
||||
};
|
||||
|
||||
virtual ~QgsDatabaseQueryLoggerNode();
|
||||
|
||||
/**
|
||||
* Returns the node's parent node.
|
||||
*
|
||||
* If parent is NULLPTR, the node is a root node
|
||||
*/
|
||||
QgsDatabaseQueryLoggerGroup *parent() { return mParent; }
|
||||
|
||||
/**
|
||||
* Returns the node's data for the specified model \a role.
|
||||
*/
|
||||
virtual QVariant data( int role = Qt::DisplayRole ) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the number of child nodes owned by this node.
|
||||
*/
|
||||
virtual int childCount() const = 0;
|
||||
|
||||
/**
|
||||
* Returns a list of actions relating to the node.
|
||||
*
|
||||
* The actions should be parented to \a parent.
|
||||
*/
|
||||
virtual QList< QAction * > actions( QObject *parent );
|
||||
|
||||
/**
|
||||
* Converts the node's contents to a variant.
|
||||
*/
|
||||
virtual QVariant toVariant() const;
|
||||
|
||||
protected:
|
||||
|
||||
QgsDatabaseQueryLoggerNode();
|
||||
|
||||
private:
|
||||
|
||||
QgsDatabaseQueryLoggerGroup *mParent = nullptr;
|
||||
friend class QgsDatabaseQueryLoggerGroup;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerGroup
|
||||
* \brief Base class for query logger model "group" nodes, which contain children of their own.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerGroup : public QgsDatabaseQueryLoggerNode
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Adds a \a child node to this node.
|
||||
*/
|
||||
void addChild( std::unique_ptr< QgsDatabaseQueryLoggerNode > child );
|
||||
|
||||
/**
|
||||
* Returns the index of the specified \a child node.
|
||||
*
|
||||
* \warning \a child must be a valid child of this node.
|
||||
*/
|
||||
int indexOf( QgsDatabaseQueryLoggerNode *child ) const;
|
||||
|
||||
/**
|
||||
* Returns the child at the specified \a index.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerNode *childAt( int index );
|
||||
|
||||
/**
|
||||
* Clears the group, removing all its children.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
int childCount() const override final { return mChildren.size(); }
|
||||
QVariant data( int role = Qt::DisplayRole ) const override;
|
||||
QVariant toVariant() const override;
|
||||
|
||||
// protected:
|
||||
|
||||
/**
|
||||
* Constructor for a QgsDatabaseQueryLoggerGroup, with the specified \a title.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerGroup( const QString &title );
|
||||
|
||||
/**
|
||||
* Adds a simple \a key: \a value node to the group.
|
||||
*/
|
||||
void addKeyValueNode( const QString &key, const QString &value, const QColor &color = QColor() );
|
||||
|
||||
private:
|
||||
|
||||
std::deque< std::unique_ptr< QgsDatabaseQueryLoggerNode > > mChildren;
|
||||
QString mGroupTitle;
|
||||
friend class QgsDatabaseQueryLoggerRootNode;
|
||||
friend class QgsAppQueryLogger;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerValueNode
|
||||
* \brief A "key: value" style node for the query logger model.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerValueNode : public QgsDatabaseQueryLoggerNode
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsDatabaseQueryLoggerValueNode, with the specified \a key (usually translated) and \a value.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerValueNode( const QString &key, const QString &value, const QColor &color = QColor() );
|
||||
|
||||
/**
|
||||
* Returns the node's key.
|
||||
*/
|
||||
QString key() const { return mKey; }
|
||||
|
||||
/**
|
||||
* Returns the node's value.
|
||||
*/
|
||||
QString value() const { return mValue; }
|
||||
|
||||
QVariant data( int role = Qt::DisplayRole ) const override final;
|
||||
int childCount() const override final { return 0; }
|
||||
QList< QAction * > actions( QObject *parent ) override final;
|
||||
|
||||
private:
|
||||
|
||||
QString mKey;
|
||||
QString mValue;
|
||||
QColor mColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerRootNode
|
||||
* \brief Root node for the query logger model.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerRootNode final : public QgsDatabaseQueryLoggerGroup
|
||||
{
|
||||
public:
|
||||
|
||||
QgsDatabaseQueryLoggerRootNode();
|
||||
QVariant data( int role = Qt::DisplayRole ) const override final;
|
||||
|
||||
/**
|
||||
* Removes a \a row from the root group.
|
||||
*/
|
||||
void removeRow( int row );
|
||||
|
||||
QVariant toVariant() const override;
|
||||
};
|
||||
|
||||
|
||||
#endif // QgsDatabaseQueryLoggerNODE_H
|
205
src/app/devtools/querylogger/qgsqueryloggerpanelwidget.cpp
Normal file
205
src/app/devtools/querylogger/qgsqueryloggerpanelwidget.cpp
Normal file
@ -0,0 +1,205 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggerpanelwidget.cpp
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 "qgsapplication.h"
|
||||
#include "qgsguiutils.h"
|
||||
#include "qgsjsonutils.h"
|
||||
#include "qgsqueryloggerpanelwidget.h"
|
||||
#include "qgsqueryloggernode.h"
|
||||
#include "qgsappquerylogger.h"
|
||||
#include "qgssettings.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include <QFontDatabase>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollBar>
|
||||
#include <QToolButton>
|
||||
#include <QCheckBox>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerTreeView
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerTreeView::QgsDatabaseQueryLoggerTreeView( QgsAppQueryLogger *logger, QWidget *parent )
|
||||
: QTreeView( parent )
|
||||
, mLogger( logger )
|
||||
{
|
||||
connect( this, &QTreeView::expanded, this, &QgsDatabaseQueryLoggerTreeView::itemExpanded );
|
||||
|
||||
setFont( QFontDatabase::systemFont( QFontDatabase::FixedFont ) );
|
||||
|
||||
mProxyModel = new QgsDatabaseQueryLoggerProxyModel( mLogger, this );
|
||||
setModel( mProxyModel );
|
||||
|
||||
setContextMenuPolicy( Qt::CustomContextMenu );
|
||||
connect( this, &QgsDatabaseQueryLoggerTreeView::customContextMenuRequested, this, &QgsDatabaseQueryLoggerTreeView::contextMenu );
|
||||
|
||||
connect( verticalScrollBar(), &QAbstractSlider::sliderMoved, this, [this]( int value )
|
||||
{
|
||||
if ( value == verticalScrollBar()->maximum() )
|
||||
mAutoScroll = true;
|
||||
else
|
||||
mAutoScroll = false;
|
||||
} );
|
||||
|
||||
connect( mLogger, &QAbstractItemModel::rowsInserted, this, [ = ]
|
||||
{
|
||||
if ( mLogger->rowCount() > ( QgsAppQueryLogger::MAX_LOGGED_REQUESTS * 1.2 ) ) // 20 % more as buffer
|
||||
{
|
||||
// never trim expanded nodes
|
||||
const int toTrim = mLogger->rowCount() - QgsAppQueryLogger::MAX_LOGGED_REQUESTS;
|
||||
int trimmed = 0;
|
||||
QList< int > rowsToTrim;
|
||||
rowsToTrim.reserve( toTrim );
|
||||
for ( int i = 0; i < mLogger->rowCount(); ++i )
|
||||
{
|
||||
const QModelIndex proxyIndex = mProxyModel->mapFromSource( mLogger->index( i, 0 ) );
|
||||
if ( !proxyIndex.isValid() || !isExpanded( proxyIndex ) )
|
||||
{
|
||||
rowsToTrim << i;
|
||||
trimmed++;
|
||||
}
|
||||
if ( trimmed == toTrim )
|
||||
break;
|
||||
}
|
||||
|
||||
mLogger->removeRequestRows( rowsToTrim );
|
||||
}
|
||||
|
||||
if ( mAutoScroll )
|
||||
scrollToBottom();
|
||||
} );
|
||||
|
||||
mMenu = new QMenu( this );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerTreeView::setFilterString( const QString &string )
|
||||
{
|
||||
mProxyModel->setFilterString( string );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerTreeView::itemExpanded( const QModelIndex &index )
|
||||
{
|
||||
// if the item is a QgsNetworkLoggerRequestGroup item, open all children (show ALL info of it)
|
||||
// we want to scroll to last request
|
||||
|
||||
// only expand all children on QgsNetworkLoggerRequestGroup nodes (which don't have a valid parent!)
|
||||
if ( !index.parent().isValid() )
|
||||
expandChildren( index );
|
||||
|
||||
// make ALL request information visible by scrolling view to it
|
||||
scrollTo( index );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerTreeView::contextMenu( QPoint point )
|
||||
{
|
||||
const QModelIndex viewModelIndex = indexAt( point );
|
||||
const QModelIndex modelIndex = mProxyModel->mapToSource( viewModelIndex );
|
||||
|
||||
if ( modelIndex.isValid() )
|
||||
{
|
||||
mMenu->clear();
|
||||
|
||||
const QList< QAction * > actions = mLogger->actions( modelIndex, mMenu );
|
||||
mMenu->addActions( actions );
|
||||
if ( !mMenu->actions().empty() )
|
||||
{
|
||||
mMenu->exec( viewport()->mapToGlobal( point ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLoggerTreeView::expandChildren( const QModelIndex &index )
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
return;
|
||||
|
||||
const int count = model()->rowCount( index );
|
||||
for ( int i = 0; i < count; ++i )
|
||||
{
|
||||
const QModelIndex childIndex = model()->index( i, 0, index );
|
||||
expandChildren( childIndex );
|
||||
}
|
||||
if ( !isExpanded( index ) )
|
||||
expand( index );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLoggerPanelWidget
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLoggerPanelWidget::QgsDatabaseQueryLoggerPanelWidget( QgsAppQueryLogger *logger, QWidget *parent )
|
||||
: QgsDevToolWidget( parent )
|
||||
, mLogger( logger )
|
||||
{
|
||||
setupUi( this );
|
||||
|
||||
mTreeView = new QgsDatabaseQueryLoggerTreeView( mLogger );
|
||||
verticalLayout->addWidget( mTreeView );
|
||||
mToolbar->setIconSize( QgsGuiUtils::iconSize( true ) );
|
||||
|
||||
mFilterLineEdit->setShowClearButton( true );
|
||||
mFilterLineEdit->setShowSearchIcon( true );
|
||||
mFilterLineEdit->setPlaceholderText( tr( "Filter requests" ) );
|
||||
|
||||
mActionRecord->setChecked( mLogger->isLogging() );
|
||||
|
||||
connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, mTreeView, &QgsDatabaseQueryLoggerTreeView::setFilterString );
|
||||
connect( mActionClear, &QAction::triggered, mLogger, &QgsAppQueryLogger::clear );
|
||||
connect( mActionRecord, &QAction::toggled, this, [ = ]( bool enabled )
|
||||
{
|
||||
QgsSettings().setValue( QStringLiteral( "logNetworkRequests" ), enabled, QgsSettings::App );
|
||||
mLogger->enableLogging( enabled );
|
||||
} );
|
||||
connect( mActionSaveLog, &QAction::triggered, this, [ = ]()
|
||||
{
|
||||
if ( QMessageBox::warning( this, tr( "Save Network Log" ),
|
||||
tr( "Security warning: network logs may contain sensitive data including usernames or passwords. Treat this log as confidential and be careful who you share it with. Continue?" ), QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No )
|
||||
return;
|
||||
|
||||
const QString saveFilePath = QFileDialog::getSaveFileName( this, tr( "Save Network Log" ), QDir::homePath(), tr( "Log files" ) + " (*.json)" );
|
||||
if ( saveFilePath.isEmpty() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QFile exportFile( saveFilePath );
|
||||
if ( !exportFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
QTextStream fout( &exportFile );
|
||||
|
||||
const QVariant value = mLogger->rootGroup()->toVariant();
|
||||
const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( value ).dump( 2 ) );
|
||||
|
||||
fout << json;
|
||||
} );
|
||||
|
||||
|
||||
QMenu *settingsMenu = new QMenu( this );
|
||||
QToolButton *settingsButton = new QToolButton();
|
||||
settingsButton->setAutoRaise( true );
|
||||
settingsButton->setToolTip( tr( "Settings" ) );
|
||||
settingsButton->setMenu( settingsMenu );
|
||||
settingsButton->setPopupMode( QToolButton::InstantPopup );
|
||||
settingsButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionOptions.svg" ) ) );
|
||||
mToolbar->addWidget( settingsButton );
|
||||
}
|
84
src/app/devtools/querylogger/qgsqueryloggerpanelwidget.h
Normal file
84
src/app/devtools/querylogger/qgsqueryloggerpanelwidget.h
Normal file
@ -0,0 +1,84 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggerpanelwidget.h
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 QgsDatabaseQueryLoggerPANELWIDGET_H
|
||||
#define QgsDatabaseQueryLoggerPANELWIDGET_H
|
||||
|
||||
#include "qgsdevtoolwidget.h"
|
||||
#include "ui_qgsqueryloggerpanelbase.h"
|
||||
#include <QTreeView>
|
||||
|
||||
class QgsAppQueryLogger;
|
||||
class QgsDatabaseQueryLoggerProxyModel;
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerTreeView
|
||||
* \brief A custom QTreeView subclass for showing logged database queries.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerTreeView: public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsDatabaseQueryLoggerTreeView, attached to the specified \a logger.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerTreeView( QgsAppQueryLogger *logger, QWidget *parent = nullptr );
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* Sets a filter \a string to apply to request URLs.
|
||||
*/
|
||||
void setFilterString( const QString &string );
|
||||
|
||||
private slots:
|
||||
void itemExpanded( const QModelIndex &index );
|
||||
void contextMenu( QPoint point );
|
||||
|
||||
private:
|
||||
|
||||
void expandChildren( const QModelIndex &index );
|
||||
QMenu *mMenu = nullptr;
|
||||
QgsAppQueryLogger *mLogger = nullptr;
|
||||
QgsDatabaseQueryLoggerProxyModel *mProxyModel = nullptr;
|
||||
bool mAutoScroll = true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup app
|
||||
* \class QgsDatabaseQueryLoggerPanelWidget
|
||||
* \brief A panel widget showing logged network requests.
|
||||
*/
|
||||
class QgsDatabaseQueryLoggerPanelWidget : public QgsDevToolWidget, private Ui::QgsDatabaseQueryLoggerPanelBase
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsDatabaseQueryLoggerPanelWidget, linked with the specified \a logger.
|
||||
*/
|
||||
QgsDatabaseQueryLoggerPanelWidget( QgsAppQueryLogger *logger, QWidget *parent );
|
||||
|
||||
private:
|
||||
|
||||
QgsDatabaseQueryLoggerTreeView *mTreeView = nullptr;
|
||||
QgsAppQueryLogger *mLogger = nullptr;
|
||||
};
|
||||
|
||||
|
||||
#endif // QgsDatabaseQueryLoggerPANELWIDGET_H
|
29
src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.cpp
Normal file
29
src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.cpp
Normal file
@ -0,0 +1,29 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggerwidgetfactory.cpp
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 "qgsqueryloggerwidgetfactory.h"
|
||||
#include "qgsqueryloggerpanelwidget.h"
|
||||
#include "qgsapplication.h"
|
||||
|
||||
QgsDatabaseQueryLoggerWidgetFactory::QgsDatabaseQueryLoggerWidgetFactory( QgsAppQueryLogger *logger )
|
||||
: QgsDevToolWidgetFactory( QObject::tr( "Query Logger" ), QgsApplication::getThemeIcon( QStringLiteral( "propertyicons/network_and_proxy.svg" ) ) )
|
||||
, mLogger( logger )
|
||||
{
|
||||
}
|
||||
|
||||
QgsDevToolWidget *QgsDatabaseQueryLoggerWidgetFactory::createWidget( QWidget *parent ) const
|
||||
{
|
||||
return new QgsDatabaseQueryLoggerPanelWidget( mLogger, parent );
|
||||
}
|
35
src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.h
Normal file
35
src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.h
Normal file
@ -0,0 +1,35 @@
|
||||
/***************************************************************************
|
||||
QgsDatabaseQueryLoggerwidgetfactory.h
|
||||
-------------------------
|
||||
begin : October 2021
|
||||
copyright : (C) 2021 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 QgsDatabaseQueryLoggerWIDGETFACTORY_H
|
||||
#define QgsDatabaseQueryLoggerWIDGETFACTORY_H
|
||||
|
||||
#include "qgsdevtoolwidgetfactory.h"
|
||||
|
||||
class QgsAppQueryLogger;
|
||||
|
||||
class QgsDatabaseQueryLoggerWidgetFactory: public QgsDevToolWidgetFactory
|
||||
{
|
||||
public:
|
||||
|
||||
QgsDatabaseQueryLoggerWidgetFactory( QgsAppQueryLogger *logger );
|
||||
QgsDevToolWidget *createWidget( QWidget *parent = nullptr ) const override;
|
||||
|
||||
private:
|
||||
|
||||
QgsAppQueryLogger *mLogger = nullptr;
|
||||
};
|
||||
|
||||
|
||||
#endif // QgsDatabaseQueryLoggerWIDGETFACTORY_H
|
@ -437,6 +437,8 @@ Q_GUI_EXPORT extern int qt_defaultDpiX();
|
||||
#include "qgsuserprofilemanager.h"
|
||||
#include "qgsuserprofile.h"
|
||||
#include "qgsnetworkloggerwidgetfactory.h"
|
||||
#include "devtools/querylogger/qgsappquerylogger.h"
|
||||
#include "devtools/querylogger/qgsqueryloggerwidgetfactory.h"
|
||||
#include "devtools/profiler/qgsprofilerwidgetfactory.h"
|
||||
#include "qgsabstractdatabaseproviderconnection.h"
|
||||
#include "qgszipitem.h"
|
||||
@ -989,6 +991,10 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
|
||||
mNetworkLogger = new QgsNetworkLogger( QgsNetworkAccessManager::instance(), this );
|
||||
endProfile();
|
||||
|
||||
startProfile( tr( "Create database query logger" ) );
|
||||
mQueryLogger = new QgsAppQueryLogger( this );
|
||||
endProfile();
|
||||
|
||||
// load GUI: actions, menus, toolbars
|
||||
startProfile( tr( "Setting up UI" ) );
|
||||
setupUi( this );
|
||||
@ -1829,6 +1835,7 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers
|
||||
mBearingNumericFormat.reset( QgsLocalDefaultSettings::bearingFormat() );
|
||||
|
||||
mNetworkLoggerWidgetFactory.reset( std::make_unique< QgsNetworkLoggerWidgetFactory >( mNetworkLogger ) );
|
||||
mQueryLoggerWidgetFactory.reset( std::make_unique< QgsDatabaseQueryLoggerWidgetFactory >( mQueryLogger ) );
|
||||
|
||||
// update windows
|
||||
qApp->processEvents();
|
||||
|
@ -151,6 +151,7 @@ class QgsDevToolsPanelWidget;
|
||||
class QgsDevToolWidgetFactory;
|
||||
class QgsNetworkLogger;
|
||||
class QgsNetworkLoggerWidgetFactory;
|
||||
class QgsAppQueryLogger;
|
||||
class QgsMapToolCapture;
|
||||
class QgsElevationProfileWidget;
|
||||
|
||||
@ -2742,6 +2743,8 @@ class APP_EXPORT QgisApp : public QMainWindow, private Ui::MainWindow
|
||||
QgsNetworkLogger *mNetworkLogger = nullptr;
|
||||
QgsScopedDevToolWidgetFactory mNetworkLoggerWidgetFactory;
|
||||
QgsScopedDevToolWidgetFactory mStartupProfilerWidgetFactory;
|
||||
QgsAppQueryLogger *mQueryLogger = nullptr;
|
||||
QgsScopedDevToolWidgetFactory mQueryLoggerWidgetFactory;
|
||||
|
||||
QgsScopedOptionsWidgetFactory mCodeEditorWidgetFactory;
|
||||
QgsScopedOptionsWidgetFactory mBabelGpsDevicesWidgetFactory;
|
||||
|
@ -355,6 +355,7 @@ set(QGIS_CORE_SRCS
|
||||
qgsdataprovidertemporalcapabilities.cpp
|
||||
qgsdatetimestatisticalsummary.cpp
|
||||
qgsdbfilterproxymodel.cpp
|
||||
qgsdbquerylog.cpp
|
||||
qgsdefaultvalue.cpp
|
||||
qgsdiagramrenderer.cpp
|
||||
qgsdistancearea.cpp
|
||||
@ -1004,6 +1005,7 @@ set(QGIS_CORE_HDRS
|
||||
qgsdatasourceuri.h
|
||||
qgsdatetimestatisticalsummary.h
|
||||
qgsdbfilterproxymodel.h
|
||||
qgsdbquerylog.h
|
||||
qgsdefaultvalue.h
|
||||
qgsdiagramrenderer.h
|
||||
qgsdistancearea.h
|
||||
|
@ -77,6 +77,7 @@
|
||||
#include "qgslocator.h"
|
||||
#include "qgsreadwritelocker.h"
|
||||
#include "qgsbabelformatregistry.h"
|
||||
#include "qgsdbquerylog.h"
|
||||
|
||||
#include "gps/qgsgpsconnectionregistry.h"
|
||||
#include "processing/qgsprocessingregistry.h"
|
||||
@ -265,6 +266,7 @@ void QgsApplication::init( QString profileFolder )
|
||||
std::call_once( sMetaTypesRegistered, []
|
||||
{
|
||||
qRegisterMetaType<QgsGeometry::Error>( "QgsGeometry::Error" );
|
||||
qRegisterMetaType<QgsDatabaseQueryLogEntry>( "QgsDatabaseQueryLogEntry" );
|
||||
qRegisterMetaType<QgsProcessingFeatureSourceDefinition>( "QgsProcessingFeatureSourceDefinition" );
|
||||
qRegisterMetaType<QgsProcessingOutputLayerDefinition>( "QgsProcessingOutputLayerDefinition" );
|
||||
qRegisterMetaType<QgsUnitTypes::LayoutUnit>( "QgsUnitTypes::LayoutUnit" );
|
||||
@ -2433,6 +2435,11 @@ QgsRecentStyleHandler *QgsApplication::recentStyleHandler()
|
||||
return members()->mRecentStyleHandler;
|
||||
}
|
||||
|
||||
QgsDatabaseQueryLog *QgsApplication::databaseQueryLog()
|
||||
{
|
||||
return members()->mQueryLogger;
|
||||
}
|
||||
|
||||
QgsStyleModel *QgsApplication::defaultStyleModel()
|
||||
{
|
||||
return members()->mStyleModel;
|
||||
@ -2512,6 +2519,11 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
|
||||
mMessageLog = new QgsMessageLog();
|
||||
QgsRuntimeProfiler *profiler = QgsRuntimeProfiler::threadLocalInstance();
|
||||
|
||||
{
|
||||
profiler->start( tr( "Create query logger" ) );
|
||||
mQueryLogger = new QgsDatabaseQueryLog();
|
||||
profiler->end();
|
||||
}
|
||||
{
|
||||
profiler->start( tr( "Setup coordinate reference system registry" ) );
|
||||
mCrsRegistry = new QgsCoordinateReferenceSystemRegistry();
|
||||
@ -2726,6 +2738,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
|
||||
delete mConnectionRegistry;
|
||||
delete mLocalizedDataPathRegistry;
|
||||
delete mCrsRegistry;
|
||||
delete mQueryLogger;
|
||||
delete mSettingsRegistryCore;
|
||||
}
|
||||
|
||||
|
@ -72,6 +72,7 @@ class QgsPointCloudRendererRegistry;
|
||||
class QgsTileDownloadManager;
|
||||
class QgsCoordinateReferenceSystemRegistry;
|
||||
class QgsRecentStyleHandler;
|
||||
class QgsDatabaseQueryLog;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
@ -820,6 +821,13 @@ class CORE_EXPORT QgsApplication : public QApplication
|
||||
*/
|
||||
static QgsRecentStyleHandler *recentStyleHandler() SIP_KEEPREFERENCE;
|
||||
|
||||
/**
|
||||
* Returns the database query log.
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
static QgsDatabaseQueryLog *databaseQueryLog() SIP_KEEPREFERENCE;
|
||||
|
||||
/**
|
||||
* Returns a shared QgsStyleModel containing the default style library (see QgsStyle::defaultStyle()).
|
||||
*
|
||||
@ -1136,6 +1144,7 @@ class CORE_EXPORT QgsApplication : public QApplication
|
||||
QgsTileDownloadManager *mTileDownloadManager = nullptr;
|
||||
QgsStyleModel *mStyleModel = nullptr;
|
||||
QgsRecentStyleHandler *mRecentStyleHandler = nullptr;
|
||||
QgsDatabaseQueryLog *mQueryLogger = nullptr;
|
||||
QString mNullRepresentation;
|
||||
QStringList mSvgPathCache;
|
||||
bool mSvgPathCacheValid = false;
|
||||
|
68
src/core/qgsdbquerylog.cpp
Normal file
68
src/core/qgsdbquerylog.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
/***************************************************************************
|
||||
qgsdbquerylog.cpp
|
||||
------------
|
||||
Date : October 2021
|
||||
Copyright : (C) 2021 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 "qgsdbquerylog.h"
|
||||
#include "qgsapplication.h"
|
||||
#include <QDateTime>
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLogEntry
|
||||
//
|
||||
|
||||
QAtomicInt QgsDatabaseQueryLogEntry::sQueryId = 0;
|
||||
|
||||
QgsDatabaseQueryLogEntry::QgsDatabaseQueryLogEntry( const QString &query )
|
||||
: queryId( ++sQueryId )
|
||||
, query( query )
|
||||
, startedTime( QDateTime::currentMSecsSinceEpoch() )
|
||||
{}
|
||||
|
||||
|
||||
//
|
||||
// QgsDatabaseQueryLog
|
||||
//
|
||||
|
||||
QgsDatabaseQueryLog::QgsDatabaseQueryLog( QObject *parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLog::log( const QgsDatabaseQueryLogEntry &query )
|
||||
{
|
||||
QMetaObject::invokeMethod( QgsApplication::databaseQueryLog(), "queryStartedPrivate", Qt::QueuedConnection, Q_ARG( QgsDatabaseQueryLogEntry, query ) );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLog::finished( const QgsDatabaseQueryLogEntry &query )
|
||||
{
|
||||
// record time of completion
|
||||
QgsDatabaseQueryLogEntry finishedQuery = query;
|
||||
finishedQuery.finishedTime = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
QMetaObject::invokeMethod( QgsApplication::databaseQueryLog(), "queryFinishedPrivate", Qt::QueuedConnection, Q_ARG( QgsDatabaseQueryLogEntry, finishedQuery ) );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLog::queryStartedPrivate( const QgsDatabaseQueryLogEntry &query )
|
||||
{
|
||||
QgsDebugMsg( query.query );
|
||||
emit queryStarted( query );
|
||||
}
|
||||
|
||||
void QgsDatabaseQueryLog::queryFinishedPrivate( const QgsDatabaseQueryLogEntry &query )
|
||||
{
|
||||
QgsDebugMsg( query.query );
|
||||
emit queryFinished( query );
|
||||
}
|
||||
|
170
src/core/qgsdbquerylog.h
Normal file
170
src/core/qgsdbquerylog.h
Normal file
@ -0,0 +1,170 @@
|
||||
/***************************************************************************
|
||||
qgsdbquerylog.h
|
||||
------------
|
||||
Date : October 2021
|
||||
Copyright : (C) 2021 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 QGSDBQUERYLOG_H
|
||||
#define QGSDBQUERYLOG_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis.h"
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsDatabaseQueryLogEntry
|
||||
* \brief Encapsulates a logged database query.
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
class CORE_EXPORT QgsDatabaseQueryLogEntry
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor for QgsDatabaseQueryLogEntry.
|
||||
*/
|
||||
QgsDatabaseQueryLogEntry( const QString &query = QString() );
|
||||
|
||||
/**
|
||||
* Unique query ID.
|
||||
*
|
||||
* This ID will automatically be set on creation of a new QgsDatabaseQueryLogEntry object.
|
||||
*/
|
||||
int queryId = 0;
|
||||
|
||||
//! The logged database query (e.g. the SQL query)
|
||||
QString query;
|
||||
|
||||
/**
|
||||
* Time when the query started (in milliseconds since epoch).
|
||||
*
|
||||
* This will be automatically recorded on creation of a new QgsDatabaseQueryLogEntry object.
|
||||
*/
|
||||
quint64 startedTime = 0;
|
||||
|
||||
/**
|
||||
* Time when the query finished (in milliseconds since epoch), if available.
|
||||
*/
|
||||
quint64 finishedTime = 0;
|
||||
|
||||
/**
|
||||
* The QGIS class which initiated the query.
|
||||
*
|
||||
* c++ code can automatically populate this through the QgsSetQueryLogClass macro.
|
||||
*/
|
||||
QString initiatorClass;
|
||||
|
||||
/**
|
||||
* Code file location for the query origin.
|
||||
*
|
||||
* c++ code can automatically populate this through the QgsSetQueryLogClass macro.
|
||||
*/
|
||||
QString origin;
|
||||
|
||||
private:
|
||||
|
||||
static QAtomicInt sQueryId;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QgsDatabaseQueryLogEntry );
|
||||
|
||||
#ifndef SIP_RUN
|
||||
#include "qgsconfig.h"
|
||||
constexpr int sQueryLoggerFilePrefixLength = CMAKE_SOURCE_DIR[sizeof( CMAKE_SOURCE_DIR ) - 1] == '/' ? sizeof( CMAKE_SOURCE_DIR ) + 1 : sizeof( CMAKE_SOURCE_DIR );
|
||||
#define QgsSetQueryLogClass(entry, _class) entry.initiatorClass = _class; entry.origin = QString(QString( __FILE__ ).mid( sQueryLoggerFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")");
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsDatabaseQueryLog
|
||||
* \brief Handles logging of database queries.
|
||||
*
|
||||
* QgsDatabaseQueryLog is not usually directly created, but rather accessed through
|
||||
* QgsApplication::databaseQueryLog(). Generally, clients should only access the
|
||||
* static log() method to register their queries.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
* \code{.py}
|
||||
* # Log a database query
|
||||
* QgsDatabaseQueryLog.log('SELECT * FROM my_table')
|
||||
* \endcode
|
||||
*
|
||||
*
|
||||
* \since QGIS 3.24
|
||||
*/
|
||||
class CORE_EXPORT QgsDatabaseQueryLog: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Creates a new query log.
|
||||
*
|
||||
* QgsDatabaseQueryLog is not usually directly created, but rather accessed through
|
||||
* QgsApplication::databaseQueryLog().
|
||||
*/
|
||||
QgsDatabaseQueryLog( QObject *parent = nullptr );
|
||||
|
||||
/**
|
||||
* Logs a database \a query as starting.
|
||||
*
|
||||
* This method can be safely called from any thread.
|
||||
*/
|
||||
static void log( const QgsDatabaseQueryLogEntry &query );
|
||||
|
||||
/**
|
||||
* Records that the database \a query as finished.
|
||||
*
|
||||
* This method can be safely called from any thread.
|
||||
*/
|
||||
static void finished( const QgsDatabaseQueryLogEntry &query );
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
* Internal slot for logging queries as start.
|
||||
*
|
||||
* \note Not available in Python bindings.
|
||||
*/
|
||||
void queryStartedPrivate( const QgsDatabaseQueryLogEntry &query ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Internal slot for logging queries as finished.
|
||||
*
|
||||
* \note Not available in Python bindings.
|
||||
*/
|
||||
void queryFinishedPrivate( const QgsDatabaseQueryLogEntry &query ) SIP_SKIP;
|
||||
|
||||
signals:
|
||||
|
||||
/**
|
||||
* Emitted whenever a database query is started.
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
*/
|
||||
void queryStarted( const QgsDatabaseQueryLogEntry &query ) SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Emitted whenever a database query has finished executing.
|
||||
*
|
||||
* \note Not available in Python bindings
|
||||
*/
|
||||
void queryFinished( const QgsDatabaseQueryLogEntry &query ) SIP_SKIP;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSDBQUERYLOG_H
|
@ -31,6 +31,7 @@
|
||||
#include "qgspostgresstringutils.h"
|
||||
#include "qgspostgresconnpool.h"
|
||||
#include "qgsvariantutils.h"
|
||||
#include "qgsdbquerylog.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QStringList>
|
||||
@ -49,6 +50,14 @@
|
||||
|
||||
const int PG_DEFAULT_TIMEOUT = 30;
|
||||
|
||||
#include "qgsconfig.h"
|
||||
constexpr int sPostgresConQueryLogFilePrefixLength = CMAKE_SOURCE_DIR[sizeof( CMAKE_SOURCE_DIR ) - 1] == '/' ? sizeof( CMAKE_SOURCE_DIR ) + 1 : sizeof( CMAKE_SOURCE_DIR );
|
||||
#define LoggedPQExecNR(query) { QgsDatabaseQueryLogEntry logEntry( query ); entry.initiatorClass = _class; entry.origin = QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")"); \
|
||||
QgsDatabaseQueryLog::log( entry ); \
|
||||
PQexecNR( query ); \
|
||||
QgsDatabaseQueryLog::finished( entry ); }
|
||||
|
||||
|
||||
QgsPostgresResult::~QgsPostgresResult()
|
||||
{
|
||||
if ( mRes )
|
||||
@ -412,8 +421,17 @@ QgsPostgresConn::QgsPostgresConn( const QString &conninfo, bool readOnly, bool s
|
||||
|
||||
if ( mPostgresqlVersion >= 90000 )
|
||||
{
|
||||
PQexecNR( QStringLiteral( "SET application_name='QGIS'" ) );
|
||||
PQexecNR( QStringLiteral( "SET extra_float_digits=3" ) );
|
||||
QString query = QStringLiteral( "SET application_name='QGIS'" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresFeatureIterator" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
PQexecNR( query );
|
||||
|
||||
query = QStringLiteral( "SET extra_float_digits=3" );
|
||||
entry = QgsDatabaseQueryLogEntry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresFeatureIterator" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
PQexecNR( query );
|
||||
}
|
||||
|
||||
PQsetNoticeProcessor( mConn, noticeProcessor, nullptr );
|
||||
@ -817,7 +835,11 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP
|
||||
QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined. The error message from the database was:\n%1\n" )
|
||||
.arg( result.PQresultErrorMessage() ),
|
||||
tr( "PostGIS" ) );
|
||||
PQexecNR( QStringLiteral( "COMMIT" ) );
|
||||
QString query = QStringLiteral( "COMMIT" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
PQexecNR( query );
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1021,7 +1043,11 @@ bool QgsPostgresConn::getSchemas( QList<QgsPostgresSchemaProperty> &schemas )
|
||||
result = PQexec( sql, true );
|
||||
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
||||
{
|
||||
PQexecNR( QStringLiteral( "COMMIT" ) );
|
||||
QString query = QStringLiteral( "COMMIT" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
PQexecNR( query );
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1428,7 +1454,7 @@ int QgsPostgresConn::PQCancel()
|
||||
return result;
|
||||
}
|
||||
|
||||
bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql )
|
||||
bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql, const QgsDatabaseQueryLogEntry &logEntry )
|
||||
{
|
||||
QMutexLocker locker( &mLock ); // to protect access to mOpenCursors
|
||||
QString preStr;
|
||||
@ -1442,8 +1468,12 @@ bool QgsPostgresConn::openCursor( const QString &cursorName, const QString &sql
|
||||
preStr = QStringLiteral( "BEGIN;" );
|
||||
}
|
||||
QgsDebugMsgLevel( QStringLiteral( "Binary cursor %1 for %2" ).arg( cursorName, sql ), 3 );
|
||||
return PQexecNR( QStringLiteral( "%1DECLARE %2 BINARY CURSOR%3 FOR %4" ).
|
||||
arg( preStr, cursorName, !mTransaction ? QString() : QStringLiteral( " WITH HOLD" ), sql ) );
|
||||
const QString query = QStringLiteral( "%1DECLARE %2 BINARY CURSOR%3 FOR %4" ).
|
||||
arg( preStr, cursorName, !mTransaction ? QString() : QStringLiteral( " WITH HOLD" ), sql );
|
||||
QgsDatabaseQueryLogEntry entry = logEntry;
|
||||
entry.query = query;
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
|
||||
bool QgsPostgresConn::closeCursor( const QString &cursorName )
|
||||
@ -1457,7 +1487,11 @@ bool QgsPostgresConn::closeCursor( const QString &cursorName )
|
||||
postStr = QStringLiteral( ";COMMIT" );
|
||||
}
|
||||
|
||||
if ( !PQexecNR( QStringLiteral( "CLOSE %1%2" ).arg( cursorName, postStr ) ) )
|
||||
QString query = QStringLiteral( "CLOSE %1%2" ).arg( cursorName, postStr );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
if ( !PQexecNR( query ) )
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@ -1495,7 +1529,11 @@ bool QgsPostgresConn::PQexecNR( const QString &query )
|
||||
|
||||
if ( PQstatus() == CONNECTION_OK )
|
||||
{
|
||||
PQexecNR( QStringLiteral( "ROLLBACK" ) );
|
||||
QString query = QStringLiteral( "ROLLBACK" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
PQexecNR( query );
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -1575,11 +1613,19 @@ bool QgsPostgresConn::begin()
|
||||
QMutexLocker locker( &mLock );
|
||||
if ( mTransaction )
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "SAVEPOINT transaction_savepoint" ) );
|
||||
QString query = QStringLiteral( "SAVEPOINT transaction_savepoint" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
else
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "BEGIN" ) );
|
||||
QString query = QStringLiteral( "BEGIN" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1588,11 +1634,19 @@ bool QgsPostgresConn::commit()
|
||||
QMutexLocker locker( &mLock );
|
||||
if ( mTransaction )
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) );
|
||||
QString query = QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
else
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "COMMIT" ) );
|
||||
QString query = QStringLiteral( "COMMIT" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1601,12 +1655,29 @@ bool QgsPostgresConn::rollback()
|
||||
QMutexLocker locker( &mLock );
|
||||
if ( mTransaction )
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "ROLLBACK TO SAVEPOINT transaction_savepoint" ) )
|
||||
&& PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) );
|
||||
QString query = QStringLiteral( "ROLLBACK TO SAVEPOINT transaction_savepoint" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
bool res = false;
|
||||
if ( PQexecNR( query ) )
|
||||
{
|
||||
query = QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
if ( PQexecNR( query ) )
|
||||
res = true;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
else
|
||||
{
|
||||
return PQexecNR( QStringLiteral( "ROLLBACK" ) );
|
||||
QString query = QStringLiteral( "ROLLBACK" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
return PQexecNR( query );
|
||||
}
|
||||
}
|
||||
|
||||
@ -1898,7 +1969,10 @@ void QgsPostgresConn::deduceEndian()
|
||||
QgsDebugMsgLevel( QStringLiteral( "Creating binary cursor" ), 2 );
|
||||
|
||||
// get the same value using a binary cursor
|
||||
openCursor( QStringLiteral( "oidcursor" ), QStringLiteral( "select regclass('pg_class')::oid" ) );
|
||||
QString query = QStringLiteral( "select regclass('pg_class')::oid" );
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresConn" );
|
||||
openCursor( QStringLiteral( "oidcursor" ), query, entry );
|
||||
|
||||
QgsDebugMsgLevel( QStringLiteral( "Fetching a record and attempting to get check endian-ness" ), 2 );
|
||||
|
||||
|
@ -36,6 +36,7 @@ extern "C"
|
||||
}
|
||||
|
||||
class QgsField;
|
||||
class QgsDatabaseQueryLogEntry;
|
||||
|
||||
//! Spatial column types
|
||||
enum QgsPostgresGeometryColumnType
|
||||
@ -250,7 +251,7 @@ class QgsPostgresConn : public QObject
|
||||
bool PQexecNR( const QString &query );
|
||||
|
||||
//! cursor handling
|
||||
bool openCursor( const QString &cursorName, const QString &declare );
|
||||
bool openCursor( const QString &cursorName, const QString &declare, const QgsDatabaseQueryLogEntry &logEntry );
|
||||
bool closeCursor( const QString &cursorName );
|
||||
|
||||
QString uniqueCursorName();
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include "qgsvectorlayerexporter.h"
|
||||
#include "qgsprojectitem.h"
|
||||
#include "qgsfieldsitem.h"
|
||||
#include "qgsdbquerylog.h"
|
||||
#include <QMessageBox>
|
||||
#include <climits>
|
||||
|
||||
@ -58,11 +59,17 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause )
|
||||
// handle deletion of views
|
||||
QString sqlViewCheck = QStringLiteral( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" )
|
||||
.arg( QgsPostgresConn::quotedValue( schemaTableName ) );
|
||||
QgsDatabaseQueryLogEntry entry( sqlViewCheck );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresUtils" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
QgsPostgresResult resViewCheck( conn->PQexec( sqlViewCheck ) );
|
||||
QString type = resViewCheck.PQgetvalue( 0, 0 );
|
||||
if ( type == QLatin1String( "v" ) || type == QLatin1String( "m" ) )
|
||||
{
|
||||
QString sql = QStringLiteral( "DROP %1VIEW %2" ).arg( type == QLatin1String( "m" ) ? QStringLiteral( "MATERIALIZED " ) : QString(), schemaTableName );
|
||||
QgsDatabaseQueryLogEntry entry( sql );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresUtils" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
QgsPostgresResult result( conn->PQexec( sql ) );
|
||||
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
||||
{
|
||||
@ -85,6 +92,9 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause )
|
||||
"AND f_table_schema=%1 AND f_table_name=%2" )
|
||||
.arg( QgsPostgresConn::quotedValue( schemaName ),
|
||||
QgsPostgresConn::quotedValue( tableName ) );
|
||||
entry = QgsDatabaseQueryLogEntry( sql );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresUtils" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
QgsPostgresResult result( conn->PQexec( sql ) );
|
||||
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
||||
{
|
||||
@ -113,6 +123,9 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause )
|
||||
QgsPostgresConn::quotedValue( tableName ) );
|
||||
}
|
||||
|
||||
entry = QgsDatabaseQueryLogEntry( sql );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresUtils" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
result = conn->PQexec( sql );
|
||||
if ( result.PQresultStatus() != PGRES_TUPLES_OK )
|
||||
{
|
||||
@ -147,6 +160,9 @@ bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceU
|
||||
QString sql = QStringLiteral( "DROP SCHEMA %1 %2" )
|
||||
.arg( schemaName, cascade ? QStringLiteral( "CASCADE" ) : QString() );
|
||||
|
||||
QgsDatabaseQueryLogEntry entry( sql );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresUtils" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
QgsPostgresResult result( conn->PQexec( sql ) );
|
||||
if ( result.PQresultStatus() != PGRES_COMMAND_OK )
|
||||
{
|
||||
@ -262,7 +278,7 @@ bool QgsPGConnectionItem::handleDrop( const QMimeData *data, const QString &toSc
|
||||
if ( srcLayer->isValid() )
|
||||
{
|
||||
uri.setDataSource( QString(), u.name, srcLayer->geometryType() != QgsWkbTypes::NullGeometry ? QStringLiteral( "geom" ) : QString() );
|
||||
QgsDebugMsgLevel( "URI " + uri.uri( false ), 2 );
|
||||
QgsDebugMsgLevel( "URI " + uri.uri( false ), 3 );
|
||||
|
||||
if ( !toSchema.isNull() )
|
||||
{
|
||||
@ -365,7 +381,7 @@ QString QgsPGLayerItem::createUri()
|
||||
if ( uri.wkbType() != QgsWkbTypes::NoGeometry && mLayerProperty.srids.at( 0 ) != std::numeric_limits<int>::min() )
|
||||
uri.setSrid( QString::number( mLayerProperty.srids.at( 0 ) ) );
|
||||
|
||||
QgsDebugMsgLevel( QStringLiteral( "layer uri: %1" ).arg( uri.uri( false ) ), 2 );
|
||||
QgsDebugMsgLevel( QStringLiteral( "layer uri: %1" ).arg( uri.uri( false ) ), 3 );
|
||||
return uri.uri( false );
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
#include "qgssettings.h"
|
||||
#include "qgsexception.h"
|
||||
#include "qgsgeometryengine.h"
|
||||
|
||||
#include "qgsdbquerylog.h"
|
||||
#include <QElapsedTimer>
|
||||
#include <QObject>
|
||||
|
||||
@ -422,8 +422,13 @@ bool QgsPostgresFeatureIterator::rewind()
|
||||
return false;
|
||||
|
||||
// move cursor to first record
|
||||
const QString query = QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName );
|
||||
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresFeatureIterator" );
|
||||
QgsDatabaseQueryLog::log( entry );
|
||||
mConn->PQexecNR( query );
|
||||
|
||||
mConn->PQexecNR( QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) );
|
||||
mFeatureQueue.clear();
|
||||
mFetched = 0;
|
||||
mLastFetch = false;
|
||||
@ -763,7 +768,9 @@ bool QgsPostgresFeatureIterator::declareCursor( const QString &whereClause, long
|
||||
if ( !orderBy.isEmpty() )
|
||||
query += QStringLiteral( " ORDER BY %1 " ).arg( orderBy );
|
||||
|
||||
if ( !mConn->openCursor( mCursorName, query ) )
|
||||
QgsDatabaseQueryLogEntry entry( query );
|
||||
QgsSetQueryLogClass( entry, "QgsPostgresFeatureIterator" );
|
||||
if ( !mConn->openCursor( mCursorName, query, entry ) )
|
||||
{
|
||||
// reloading the fields might help next time around
|
||||
// TODO how to cleanly force reload of fields? P->loadFields();
|
||||
|
117
src/ui/qgsqueryloggerpanelbase.ui
Normal file
117
src/ui/qgsqueryloggerpanelbase.ui
Normal file
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QgsDatabaseQueryLoggerPanelBase</class>
|
||||
<widget class="QgsPanelWidget" name="QgsDatabaseQueryLoggerPanelBase">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>700</width>
|
||||
<height>629</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QToolBar" name="mToolbar">
|
||||
<property name="iconSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="floatable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<addaction name="mActionRecord"/>
|
||||
<addaction name="mActionClear"/>
|
||||
<addaction name="mActionSaveLog"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QgsFilterLineEdit" name="mFilterLineEdit"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>6</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
<action name="mActionClear">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Clear Log</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionRecord">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionRecord.svg</normaloff>:/images/themes/default/mActionRecord.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Record Log</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="mActionSaveLog">
|
||||
<property name="icon">
|
||||
<iconset resource="../../images/images.qrc">
|
||||
<normaloff>:/images/themes/default/mActionFileSave.svg</normaloff>:/images/themes/default/mActionFileSave.svg</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Log…</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QgsPanelWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgspanelwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsFilterLineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>qgsfilterlineedit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../images/images.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
Loading…
x
Reference in New Issue
Block a user