diff --git a/images/images.qrc b/images/images.qrc index c38841ac365..9381c209592 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -591,6 +591,7 @@ themes/default/propertyicons/attributes.svg themes/default/propertyicons/CRS.svg themes/default/propertyicons/datadefined.svg + themes/default/propertyicons/database.svg themes/default/propertyicons/diagram.svg themes/default/propertyicons/digitizing.svg themes/default/propertyicons/display.svg diff --git a/images/themes/default/propertyicons/database.svg b/images/themes/default/propertyicons/database.svg new file mode 100644 index 00000000000..3018057f41c --- /dev/null +++ b/images/themes/default/propertyicons/database.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/python/core/auto_generated/qgsapplication.sip.in b/python/core/auto_generated/qgsapplication.sip.in index f16bbd66913..106a2bef57c 100644 --- a/python/core/auto_generated/qgsapplication.sip.in +++ b/python/core/auto_generated/qgsapplication.sip.in @@ -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(); diff --git a/python/core/auto_generated/qgsdbquerylog.sip.in b/python/core/auto_generated/qgsdbquerylog.sip.in new file mode 100644 index 00000000000..a8ce8473683 --- /dev/null +++ b/python/core/auto_generated/qgsdbquerylog.sip.in @@ -0,0 +1,127 @@ +/************************************************************************ + * 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 + + int queryId; + + QString uri; + + QString provider; + + QString query; + + quint64 startedTime; + + quint64 finishedTime; + + QString initiatorClass; + + QString origin; + + long long fetchedRows; + + QString error; + + bool canceled; + +}; + + + +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 + QgsDatabaseQueryLog.log('SELECT * FROM my_table') + +.. versionadded:: 3.24 +%End + +%TypeHeaderCode +#include "qgsdbquerylog.h" +%End + public: + + QgsDatabaseQueryLog( QObject *parent = 0 ); +%Docstring +Creates a new query log. + +QgsDatabaseQueryLog is not usually directly created, but rather accessed through +:py:func:`QgsApplication.databaseQueryLog()`. +%End + + + static bool enabled(); +%Docstring +Returns ``True`` if logging is enabled. + +.. seealso:: :py:func:`setEnabled` +%End + + static void log( const QgsDatabaseQueryLogEntry &query ); +%Docstring +Logs a database ``query`` as starting. + +This method can be safely called from any thread. +%End + + static void finished( const QgsDatabaseQueryLogEntry &query ); +%Docstring +Records that the database ``query`` has finished. + +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 * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 914ecb7793e..f149af7e2f9 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -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 diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index afb2228fbac..b3d4784632c 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -163,12 +163,17 @@ set(QGIS_APP_SRCS browser/qgsinbuiltdataitemproviders.cpp devtools/qgsappdevtoolutils.cpp + devtools/qgsdevtoolsmodelnode.cpp devtools/networklogger/qgsnetworklogger.cpp devtools/networklogger/qgsnetworkloggernode.cpp devtools/networklogger/qgsnetworkloggerpanelwidget.cpp devtools/networklogger/qgsnetworkloggerwidgetfactory.cpp devtools/profiler/qgsprofilerpanelwidget.cpp devtools/profiler/qgsprofilerwidgetfactory.cpp + devtools/querylogger/qgsappquerylogger.cpp + devtools/querylogger/qgsdatabasequeryloggernode.cpp + devtools/querylogger/qgsqueryloggerpanelwidget.cpp + devtools/querylogger/qgsqueryloggerwidgetfactory.cpp dwg/qgsdwgimportdialog.cpp dwg/qgsdwgimporter.cpp diff --git a/src/app/devtools/networklogger/qgsnetworklogger.cpp b/src/app/devtools/networklogger/qgsnetworklogger.cpp index 48fc14cfdc0..703b921201b 100644 --- a/src/app/devtools/networklogger/qgsnetworklogger.cpp +++ b/src/app/devtools/networklogger/qgsnetworklogger.cpp @@ -147,24 +147,24 @@ void QgsNetworkLogger::requestEncounteredSslErrors( int requestId, const QList( index.internalPointer() ); + return reinterpret_cast( index.internalPointer() ); } QList QgsNetworkLogger::actions( const QModelIndex &index, QObject *parent ) { - QgsNetworkLoggerNode *node = index2node( index ); + QgsDevToolsModelNode *node = index2node( index ); if ( !node ) return QList< QAction * >(); return node->actions( parent ); } -QModelIndex QgsNetworkLogger::node2index( QgsNetworkLoggerNode *node ) const +QModelIndex QgsNetworkLogger::node2index( QgsDevToolsModelNode *node ) const { if ( !node || !node->parent() ) return QModelIndex(); // this is the only root item -> invalid index @@ -176,11 +176,11 @@ QModelIndex QgsNetworkLogger::node2index( QgsNetworkLoggerNode *node ) const return index( row, 0, parentIndex ); } -QModelIndex QgsNetworkLogger::indexOfParentLayerTreeNode( QgsNetworkLoggerNode *parentNode ) const +QModelIndex QgsNetworkLogger::indexOfParentLayerTreeNode( QgsDevToolsModelNode *parentNode ) const { Q_ASSERT( parentNode ); - QgsNetworkLoggerGroup *grandParentNode = parentNode->parent(); + QgsDevToolsModelGroup *grandParentNode = parentNode->parent(); if ( !grandParentNode ) return QModelIndex(); // root node -> invalid index @@ -197,7 +197,7 @@ void QgsNetworkLogger::removeRequestRows( const QList &rows ) for ( int row : std::as_const( res ) ) { - int popId = data( index( row, 0, QModelIndex() ), QgsNetworkLoggerNode::RoleId ).toInt(); + int popId = data( index( row, 0, QModelIndex() ), QgsDevToolsModelNode::RoleId ).toInt(); mRequestGroups.remove( popId ); beginRemoveRows( QModelIndex(), row, row ); @@ -213,7 +213,7 @@ QgsNetworkLoggerRootNode *QgsNetworkLogger::rootGroup() int QgsNetworkLogger::rowCount( const QModelIndex &parent ) const { - QgsNetworkLoggerNode *n = index2node( parent ); + QgsDevToolsModelNode *n = index2node( parent ); if ( !n ) return 0; @@ -232,7 +232,7 @@ QModelIndex QgsNetworkLogger::index( int row, int column, const QModelIndex &par row < 0 || row >= rowCount( parent ) ) return QModelIndex(); - QgsNetworkLoggerGroup *n = dynamic_cast< QgsNetworkLoggerGroup * >( index2node( parent ) ); + QgsDevToolsModelGroup *n = dynamic_cast< QgsDevToolsModelGroup * >( index2node( parent ) ); if ( !n ) return QModelIndex(); // have no children @@ -244,7 +244,7 @@ QModelIndex QgsNetworkLogger::parent( const QModelIndex &child ) const if ( !child.isValid() ) return QModelIndex(); - if ( QgsNetworkLoggerNode *n = index2node( child ) ) + if ( QgsDevToolsModelNode *n = index2node( child ) ) { return indexOfParentLayerTreeNode( n->parent() ); // must not be null } @@ -260,7 +260,7 @@ QVariant QgsNetworkLogger::data( const QModelIndex &index, int role ) const if ( !index.isValid() || index.column() > 1 ) return QVariant(); - QgsNetworkLoggerNode *node = index2node( index ); + QgsDevToolsModelNode *node = index2node( index ); if ( !node ) return QVariant(); @@ -324,7 +324,7 @@ void QgsNetworkLoggerProxyModel::setShowCached( bool show ) bool QgsNetworkLoggerProxyModel::filterAcceptsRow( int source_row, const QModelIndex &source_parent ) const { - QgsNetworkLoggerNode *node = mLogger->index2node( mLogger->index( source_row, 0, source_parent ) ); + QgsDevToolsModelNode *node = mLogger->index2node( mLogger->index( source_row, 0, source_parent ) ); if ( QgsNetworkLoggerRequestGroup *request = dynamic_cast< QgsNetworkLoggerRequestGroup * >( node ) ) { if ( ( request->status() == QgsNetworkLoggerRequestGroup::Status::Complete || request->status() == QgsNetworkLoggerRequestGroup::Status::Canceled ) diff --git a/src/app/devtools/networklogger/qgsnetworklogger.h b/src/app/devtools/networklogger/qgsnetworklogger.h index 2351519712a..792d796ada1 100644 --- a/src/app/devtools/networklogger/qgsnetworklogger.h +++ b/src/app/devtools/networklogger/qgsnetworklogger.h @@ -20,7 +20,7 @@ #include #include "qgsnetworkaccessmanager.h" -class QgsNetworkLoggerNode; +class QgsDevToolsModelNode; class QgsNetworkLoggerRequestGroup; class QgsNetworkLoggerRootNode; class QAction; @@ -66,7 +66,7 @@ class QgsNetworkLogger : public QAbstractItemModel /** * Returns node for given index. Returns root node for invalid index. */ - QgsNetworkLoggerNode *index2node( const QModelIndex &index ) const; + QgsDevToolsModelNode *index2node( const QModelIndex &index ) const; /** * Returns a list of actions corresponding to the item at the specified \a index. @@ -109,8 +109,8 @@ class QgsNetworkLogger : public QAbstractItemModel private: //! Returns index for a given node - QModelIndex node2index( QgsNetworkLoggerNode *node ) const; - QModelIndex indexOfParentLayerTreeNode( QgsNetworkLoggerNode *parentNode ) const; + QModelIndex node2index( QgsDevToolsModelNode *node ) const; + QModelIndex indexOfParentLayerTreeNode( QgsDevToolsModelNode *parentNode ) const; QgsNetworkAccessManager *mNam = nullptr; bool mIsLogging = false; diff --git a/src/app/devtools/networklogger/qgsnetworkloggernode.cpp b/src/app/devtools/networklogger/qgsnetworkloggernode.cpp index 7842100cbb7..cd2f96a6117 100644 --- a/src/app/devtools/networklogger/qgsnetworkloggernode.cpp +++ b/src/app/devtools/networklogger/qgsnetworkloggernode.cpp @@ -26,89 +26,12 @@ #include #include -// -// QgsNetworkLoggerNode -// - -QgsNetworkLoggerNode::QgsNetworkLoggerNode() = default; -QgsNetworkLoggerNode::~QgsNetworkLoggerNode() = default; - - -// -// QgsNetworkLoggerGroup -// - -QgsNetworkLoggerGroup::QgsNetworkLoggerGroup( const QString &title ) - : mGroupTitle( title ) -{ -} - -void QgsNetworkLoggerGroup::addChild( std::unique_ptr child ) -{ - if ( !child ) - return; - - Q_ASSERT( !child->mParent ); - child->mParent = this; - - mChildren.emplace_back( std::move( child ) ); -} - -int QgsNetworkLoggerGroup::indexOf( QgsNetworkLoggerNode *child ) const -{ - Q_ASSERT( child->mParent == this ); - auto it = std::find_if( mChildren.begin(), mChildren.end(), [&]( const std::unique_ptr &p ) - { - return p.get() == child; - } ); - if ( it != mChildren.end() ) - return std::distance( mChildren.begin(), it ); - return -1; -} - -QgsNetworkLoggerNode *QgsNetworkLoggerGroup::childAt( int index ) -{ - Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() ); - return mChildren[ index ].get(); -} - -void QgsNetworkLoggerGroup::clear() -{ - mChildren.clear(); -} - -QVariant QgsNetworkLoggerGroup::data( int role ) const -{ - switch ( role ) - { - case Qt::DisplayRole: - return mGroupTitle; - - default: - break; - } - return QVariant(); -} - -QVariant QgsNetworkLoggerGroup::toVariant() const -{ - QVariantMap res; - for ( const std::unique_ptr< QgsNetworkLoggerNode > &child : mChildren ) - { - if ( const QgsNetworkLoggerValueNode *valueNode = dynamic_cast< const QgsNetworkLoggerValueNode *>( child.get() ) ) - { - res.insert( valueNode->key(), valueNode->value() ); - } - } - return res; -} - // // QgsNetworkLoggerRootNode // QgsNetworkLoggerRootNode::QgsNetworkLoggerRootNode() - : QgsNetworkLoggerGroup( QString() ) + : QgsDevToolsModelGroup( QString() ) { } @@ -126,76 +49,18 @@ void QgsNetworkLoggerRootNode::removeRow( int row ) QVariant QgsNetworkLoggerRootNode::toVariant() const { QVariantList res; - for ( const std::unique_ptr< QgsNetworkLoggerNode > &child : mChildren ) + for ( const std::unique_ptr< QgsDevToolsModelNode > &child : mChildren ) res << child->toVariant(); return res; } -// -// QgsNetworkLoggerValueNode -// -QgsNetworkLoggerValueNode::QgsNetworkLoggerValueNode( const QString &key, const QString &value, const QColor &color ) - : mKey( key ) - , mValue( value ) - , mColor( color ) -{ - -} - -QVariant QgsNetworkLoggerValueNode::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 QgsNetworkLoggerValueNode::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; -} - -// -// QgsNetworkLoggerGroup -// - -void QgsNetworkLoggerGroup::addKeyValueNode( const QString &key, const QString &value, const QColor &color ) -{ - addChild( std::make_unique< QgsNetworkLoggerValueNode >( key, value, color ) ); -} - - // // QgsNetworkLoggerRequestGroup // QgsNetworkLoggerRequestGroup::QgsNetworkLoggerRequestGroup( const QgsNetworkRequestParameters &request ) - : QgsNetworkLoggerGroup( QString() ) + : QgsDevToolsModelGroup( QString() ) , mUrl( request.request().url() ) , mRequestId( request.requestId() ) , mOperation( request.operation() ) @@ -478,7 +343,7 @@ QString QgsNetworkLoggerRequestGroup::cacheControlToString( QNetworkRequest::Cac // QgsNetworkLoggerRequestDetailsGroup::QgsNetworkLoggerRequestDetailsGroup( const QgsNetworkRequestParameters &request ) - : QgsNetworkLoggerGroup( QObject::tr( "Request" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Request" ) ) { addKeyValueNode( QObject::tr( "Operation" ), QgsNetworkLoggerRequestGroup::operationToString( request.operation() ) ); addKeyValueNode( QObject::tr( "Thread" ), request.originatingThreadId() ); @@ -521,7 +386,7 @@ QgsNetworkLoggerRequestDetailsGroup::QgsNetworkLoggerRequestDetailsGroup( const QVariant QgsNetworkLoggerRequestDetailsGroup::toVariant() const { - QVariantMap res = QgsNetworkLoggerGroup::toVariant().toMap(); + QVariantMap res = QgsDevToolsModelGroup::toVariant().toMap(); if ( mQueryGroup ) res.insert( QObject::tr( "Query" ), mQueryGroup->toVariant() ); if ( mRequestHeaders ) @@ -537,7 +402,7 @@ QVariant QgsNetworkLoggerRequestDetailsGroup::toVariant() const // QgsNetworkLoggerRequestQueryGroup::QgsNetworkLoggerRequestQueryGroup( const QUrl &url ) - : QgsNetworkLoggerGroup( QObject::tr( "Query" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Query" ) ) { QUrlQuery query( url ); const QList > queryItems = query.queryItems(); @@ -553,7 +418,7 @@ QgsNetworkLoggerRequestQueryGroup::QgsNetworkLoggerRequestQueryGroup( const QUrl // QgsNetworkLoggerRequestHeadersGroup // QgsNetworkLoggerRequestHeadersGroup::QgsNetworkLoggerRequestHeadersGroup( const QgsNetworkRequestParameters &request ) - : QgsNetworkLoggerGroup( QObject::tr( "Headers" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Headers" ) ) { const QList headers = request.request().rawHeaderList(); for ( const QByteArray &header : headers ) @@ -567,7 +432,7 @@ QgsNetworkLoggerRequestHeadersGroup::QgsNetworkLoggerRequestHeadersGroup( const // QgsNetworkLoggerPostContentGroup::QgsNetworkLoggerPostContentGroup( const QgsNetworkRequestParameters ¶meters ) - : QgsNetworkLoggerGroup( QObject::tr( "Content" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Content" ) ) { addKeyValueNode( QObject::tr( "Data" ), parameters.content() ); } @@ -578,7 +443,7 @@ QgsNetworkLoggerPostContentGroup::QgsNetworkLoggerPostContentGroup( const QgsNet // QgsNetworkLoggerReplyGroup::QgsNetworkLoggerReplyGroup( const QgsNetworkReplyContent &reply ) - : QgsNetworkLoggerGroup( QObject::tr( "Reply" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Reply" ) ) { addKeyValueNode( QObject::tr( "Status" ), reply.attribute( QNetworkRequest::HttpStatusCodeAttribute ).toString() ); if ( reply.error() != QNetworkReply::NoError ) @@ -595,7 +460,7 @@ QgsNetworkLoggerReplyGroup::QgsNetworkLoggerReplyGroup( const QgsNetworkReplyCon QVariant QgsNetworkLoggerReplyGroup::toVariant() const { - QVariantMap res = QgsNetworkLoggerGroup::toVariant().toMap(); + QVariantMap res = QgsDevToolsModelGroup::toVariant().toMap(); if ( mReplyHeaders ) { res.insert( QObject::tr( "Headers" ), mReplyHeaders->toVariant() ); @@ -608,7 +473,7 @@ QVariant QgsNetworkLoggerReplyGroup::toVariant() const // QgsNetworkLoggerReplyHeadersGroup // QgsNetworkLoggerReplyHeadersGroup::QgsNetworkLoggerReplyHeadersGroup( const QgsNetworkReplyContent &reply ) - : QgsNetworkLoggerGroup( QObject::tr( "Headers" ) ) + : QgsDevToolsModelGroup( QObject::tr( "Headers" ) ) { const QList headers = reply.rawHeaderList(); for ( const QByteArray &header : headers ) @@ -621,7 +486,7 @@ QgsNetworkLoggerReplyHeadersGroup::QgsNetworkLoggerReplyHeadersGroup( const QgsN // QgsNetworkLoggerSslErrorGroup // QgsNetworkLoggerSslErrorGroup::QgsNetworkLoggerSslErrorGroup( const QList &errors ) - : QgsNetworkLoggerGroup( QObject::tr( "SSL errors" ) ) + : QgsDevToolsModelGroup( QObject::tr( "SSL errors" ) ) { for ( const QSslError &error : errors ) { @@ -634,15 +499,5 @@ QVariant QgsNetworkLoggerSslErrorGroup::data( int role ) const if ( role == Qt::ForegroundRole ) return QBrush( QColor( 180, 65, 210 ) ); - return QgsNetworkLoggerGroup::data( role ); -} - -QList QgsNetworkLoggerNode::actions( QObject * ) -{ - return QList< QAction * >(); -} - -QVariant QgsNetworkLoggerNode::toVariant() const -{ - return QVariant(); + return QgsDevToolsModelGroup::data( role ); } diff --git a/src/app/devtools/networklogger/qgsnetworkloggernode.h b/src/app/devtools/networklogger/qgsnetworkloggernode.h index fea03444908..9301db62342 100644 --- a/src/app/devtools/networklogger/qgsnetworkloggernode.h +++ b/src/app/devtools/networklogger/qgsnetworkloggernode.h @@ -16,6 +16,7 @@ #define QGSNETWORKLOGGERNODE_H #include "qgsnetworkaccessmanager.h" +#include "devtools/qgsdevtoolsmodelnode.h" #include #include #include @@ -24,160 +25,6 @@ #include class QAction; -class QgsNetworkLoggerGroup; - -/** - * \ingroup app - * \class QgsNetworkLoggerNode - * \brief Base class for nodes in the network logger model. - * - * \since QGIS 3.14 - */ -class QgsNetworkLoggerNode -{ - public: - - //! Custom node data roles - enum Roles - { - RoleStatus = Qt::UserRole + 1, //!< Request status role - RoleId, //!< Request ID role - }; - - virtual ~QgsNetworkLoggerNode(); - - /** - * Returns the node's parent node. - * - * If parent is NULLPTR, the node is a root node - */ - QgsNetworkLoggerGroup *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: - - QgsNetworkLoggerNode(); - - private: - - QgsNetworkLoggerGroup *mParent = nullptr; - friend class QgsNetworkLoggerGroup; -}; - -/** - * \ingroup app - * \class QgsNetworkLoggerGroup - * \brief Base class for network logger model "group" nodes, which contain children of their own. - * - * \since QGIS 3.14 - */ -class QgsNetworkLoggerGroup : public QgsNetworkLoggerNode -{ - public: - - /** - * Adds a \a child node to this node. - */ - void addChild( std::unique_ptr< QgsNetworkLoggerNode > child ); - - /** - * Returns the index of the specified \a child node. - * - * \warning \a child must be a valid child of this node. - */ - int indexOf( QgsNetworkLoggerNode *child ) const; - - /** - * Returns the child at the specified \a index. - */ - QgsNetworkLoggerNode *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 QgsNetworkLoggerGroup, with the specified \a title. - */ - QgsNetworkLoggerGroup( 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< QgsNetworkLoggerNode > > mChildren; - QString mGroupTitle; - friend class QgsNetworkLoggerRootNode; - -}; - -/** - * \ingroup app - * \class QgsNetworkLoggerValueNode - * \brief A "key: value" style node for the network logger model. - * - * \since QGIS 3.14 - */ -class QgsNetworkLoggerValueNode : public QgsNetworkLoggerNode -{ - public: - - /** - * Constructor for QgsNetworkLoggerValueNode, with the specified \a key (usually translated) and \a value. - */ - QgsNetworkLoggerValueNode( 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 @@ -186,7 +33,7 @@ class QgsNetworkLoggerValueNode : public QgsNetworkLoggerNode * * \since QGIS 3.14 */ -class QgsNetworkLoggerRootNode final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerRootNode final : public QgsDevToolsModelGroup { public: @@ -231,7 +78,7 @@ class QgsNetworkLoggerSslErrorGroup; * * \since QGIS 3.14 */ -class QgsNetworkLoggerRequestGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerRequestGroup final : public QgsDevToolsModelGroup { public: @@ -357,7 +204,7 @@ class QgsNetworkLoggerPostContentGroup; * * \since QGIS 3.14 */ -class QgsNetworkLoggerRequestDetailsGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerRequestDetailsGroup final : public QgsDevToolsModelGroup { public: @@ -389,7 +236,7 @@ class QgsNetworkLoggerRequestDetailsGroup final : public QgsNetworkLoggerGroup * * \since QGIS 3.14 */ -class QgsNetworkLoggerRequestHeadersGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerRequestHeadersGroup final : public QgsDevToolsModelGroup { public: @@ -415,7 +262,7 @@ class QgsNetworkLoggerRequestHeadersGroup final : public QgsNetworkLoggerGroup * * \since QGIS 3.14 */ -class QgsNetworkLoggerRequestQueryGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerRequestQueryGroup final : public QgsDevToolsModelGroup { public: @@ -439,7 +286,7 @@ class QgsNetworkLoggerRequestQueryGroup final : public QgsNetworkLoggerGroup * * \since QGIS 3.14 */ -class QgsNetworkLoggerPostContentGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerPostContentGroup final : public QgsDevToolsModelGroup { public: @@ -468,7 +315,7 @@ class QgsNetworkLoggerReplyHeadersGroup; * * \since QGIS 3.14 */ -class QgsNetworkLoggerReplyGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerReplyGroup final : public QgsDevToolsModelGroup { public: @@ -499,7 +346,7 @@ class QgsNetworkLoggerReplyGroup final : public QgsNetworkLoggerGroup * * \since QGIS 3.14 */ -class QgsNetworkLoggerReplyHeadersGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerReplyHeadersGroup final : public QgsDevToolsModelGroup { public: @@ -525,7 +372,7 @@ class QgsNetworkLoggerReplyHeadersGroup final : public QgsNetworkLoggerGroup * * \since QGIS 3.14 */ -class QgsNetworkLoggerSslErrorGroup final : public QgsNetworkLoggerGroup +class QgsNetworkLoggerSslErrorGroup final : public QgsDevToolsModelGroup { public: diff --git a/src/app/devtools/qgsdevtoolsmodelnode.cpp b/src/app/devtools/qgsdevtoolsmodelnode.cpp new file mode 100644 index 00000000000..8ccd34d8fa4 --- /dev/null +++ b/src/app/devtools/qgsdevtoolsmodelnode.cpp @@ -0,0 +1,173 @@ +/*************************************************************************** + qgsdevtoolsmodelnode.cpp + ------------------------- + begin : March 2020 + copyright : (C) 2020 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 "qgsdevtoolsmodelnode.h" +#include "qgis.h" +#include "qgsjsonutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// QgsDevToolsModelNode +// + +QgsDevToolsModelNode::QgsDevToolsModelNode() = default; +QgsDevToolsModelNode::~QgsDevToolsModelNode() = default; + +QVariant QgsDevToolsModelNode::toVariant() const +{ + return QVariant(); +} + +QList QgsDevToolsModelNode::actions( QObject * ) +{ + return QList< QAction * >(); +} + + +// +// QgsDevToolsModelGroup +// + +QgsDevToolsModelGroup::QgsDevToolsModelGroup( const QString &title ) + : mGroupTitle( title ) +{ +} + +void QgsDevToolsModelGroup::addChild( std::unique_ptr child ) +{ + if ( !child ) + return; + + Q_ASSERT( !child->mParent ); + child->mParent = this; + + mChildren.emplace_back( std::move( child ) ); +} + +int QgsDevToolsModelGroup::indexOf( QgsDevToolsModelNode *child ) const +{ + Q_ASSERT( child->mParent == this ); + auto it = std::find_if( mChildren.begin(), mChildren.end(), [&]( const std::unique_ptr &p ) + { + return p.get() == child; + } ); + if ( it != mChildren.end() ) + return std::distance( mChildren.begin(), it ); + return -1; +} + +QgsDevToolsModelNode *QgsDevToolsModelGroup::childAt( int index ) +{ + Q_ASSERT( static_cast< std::size_t >( index ) < mChildren.size() ); + return mChildren[ index ].get(); +} + +void QgsDevToolsModelGroup::clear() +{ + mChildren.clear(); +} + +QVariant QgsDevToolsModelGroup::data( int role ) const +{ + switch ( role ) + { + case Qt::DisplayRole: + return mGroupTitle; + + default: + break; + } + return QVariant(); +} + +QVariant QgsDevToolsModelGroup::toVariant() const +{ + QVariantMap res; + for ( const std::unique_ptr< QgsDevToolsModelNode > &child : mChildren ) + { + if ( const QgsDevToolsModelValueNode *valueNode = dynamic_cast< const QgsDevToolsModelValueNode *>( child.get() ) ) + { + res.insert( valueNode->key(), valueNode->value() ); + } + } + return res; +} + + +// +// QgsDevToolsModelValueNode +// +QgsDevToolsModelValueNode::QgsDevToolsModelValueNode( const QString &key, const QString &value, const QColor &color ) + : mKey( key ) + , mValue( value ) + , mColor( color ) +{ + +} + +QVariant QgsDevToolsModelValueNode::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 QgsDevToolsModelValueNode::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; +} + +// +// QgsDevToolsModelGroup +// + +void QgsDevToolsModelGroup::addKeyValueNode( const QString &key, const QString &value, const QColor &color ) +{ + addChild( std::make_unique< QgsDevToolsModelValueNode >( key, value, color ) ); +} + diff --git a/src/app/devtools/qgsdevtoolsmodelnode.h b/src/app/devtools/qgsdevtoolsmodelnode.h new file mode 100644 index 00000000000..4b2c99ea944 --- /dev/null +++ b/src/app/devtools/qgsdevtoolsmodelnode.h @@ -0,0 +1,176 @@ +/*************************************************************************** + qgsdevtoolsmodelnode.h + ------------------------- + begin : March 2020 + copyright : (C) 2020 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 QGSDEVTOOLSMODELNODE_H +#define QGSDEVTOOLSMODELNODE_H + +#include +#include +#include +#include +#include +#include + +class QAction; +class QgsDevToolsModelGroup; + +/** + * \ingroup app + * \class QgsDevToolsModelNode + * \brief Base class for nodes in a dev tools model. + */ +class QgsDevToolsModelNode +{ + public: + + //! Custom node data roles + enum Roles + { + RoleStatus = Qt::UserRole + 1, //!< Request status role + RoleId, //!< Request ID role + }; + + virtual ~QgsDevToolsModelNode(); + + /** + * Returns the node's parent node. + * + * If parent is NULLPTR, the node is a root node + */ + QgsDevToolsModelGroup *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: + + QgsDevToolsModelNode(); + + private: + + QgsDevToolsModelGroup *mParent = nullptr; + friend class QgsDevToolsModelGroup; +}; + +/** + * \ingroup app + * \class QgsDevToolsModelGroup + * \brief Base class for dev tools model "group" nodes, which contain children of their own. + */ +class QgsDevToolsModelGroup : public QgsDevToolsModelNode +{ + public: + + /** + * Adds a \a child node to this node. + */ + void addChild( std::unique_ptr< QgsDevToolsModelNode > child ); + + /** + * Returns the index of the specified \a child node. + * + * \warning \a child must be a valid child of this node. + */ + int indexOf( QgsDevToolsModelNode *child ) const; + + /** + * Returns the child at the specified \a index. + */ + QgsDevToolsModelNode *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 QgsDevToolsModelGroup, with the specified \a title. + */ + QgsDevToolsModelGroup( 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() ); + + protected: + std::deque< std::unique_ptr< QgsDevToolsModelNode > > mChildren; + + private: + + QString mGroupTitle; + +}; + +/** + * \ingroup app + * \class QgsDevToolsModelValueNode + * \brief A "key: value" style node for a dev tools model. + */ +class QgsDevToolsModelValueNode : public QgsDevToolsModelNode +{ + public: + + /** + * Constructor for QgsDevToolsModelValueNode, with the specified \a key (usually translated) and \a value. + */ + QgsDevToolsModelValueNode( 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; +}; + +#endif // QGSDEVTOOLSMODELNODE_H diff --git a/src/app/devtools/querylogger/qgsappquerylogger.cpp b/src/app/devtools/querylogger/qgsappquerylogger.cpp new file mode 100644 index 00000000000..80f60be0774 --- /dev/null +++ b/src/app/devtools/querylogger/qgsappquerylogger.cpp @@ -0,0 +1,262 @@ +/*************************************************************************** + 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 "qgsdatabasequeryloggernode.h" +#include "qgsapplication.h" +#include "devtools/qgsdevtoolsmodelnode.h" +#include "qgssettings.h" +#include "qgis.h" +#include +#include +#include + +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() ); + + connect( QgsApplication::databaseQueryLog(), &QgsDatabaseQueryLog::queryStarted, this, &QgsAppQueryLogger::queryLogged ); + connect( QgsApplication::databaseQueryLog(), &QgsDatabaseQueryLog::queryFinished, this, &QgsAppQueryLogger::queryFinished ); +} + +QgsAppQueryLogger::~QgsAppQueryLogger() = default; + +void QgsAppQueryLogger::clear() +{ + beginResetModel(); + mQueryGroups.clear(); + mRootNode->clear(); + endResetModel(); +} + +void QgsAppQueryLogger::queryLogged( const QgsDatabaseQueryLogEntry &query ) +{ + const int childCount = mRootNode->childCount(); + + beginInsertRows( QModelIndex(), childCount, childCount ); + + std::unique_ptr< QgsDatabaseQueryLoggerQueryGroup > group = std::make_unique< QgsDatabaseQueryLoggerQueryGroup >( query ); + mQueryGroups.insert( query.queryId, group.get() ); + mRootNode->addChild( std::move( group ) ); + endInsertRows(); +} + +void QgsAppQueryLogger::queryFinished( const QgsDatabaseQueryLogEntry &query ) +{ + QgsDatabaseQueryLoggerQueryGroup *queryGroup = mQueryGroups.value( query.queryId ); + if ( !queryGroup ) + return; + + // find the row: the position of the request in the rootNode + const QModelIndex requestIndex = node2index( queryGroup ); + if ( !requestIndex.isValid() ) + return; + + if ( query.query != queryGroup->sql() ) + { + queryGroup->setSql( query.query ); + } + + // Calculate the number of children: if error or not fetched rows 1 row is added else 2 rows are added + beginInsertRows( requestIndex, queryGroup->childCount(), queryGroup->childCount() + ( query.fetchedRows != -1 ? 1 : 0 ) ); + queryGroup->setFinished( query ); + endInsertRows(); + + emit dataChanged( requestIndex, requestIndex ); +} + +QgsDevToolsModelNode *QgsAppQueryLogger::index2node( const QModelIndex &index ) const +{ + if ( !index.isValid() ) + return mRootNode.get(); + + return reinterpret_cast( index.internalPointer() ); +} + +QList QgsAppQueryLogger::actions( const QModelIndex &index, QObject *parent ) +{ + QgsDevToolsModelNode *node = index2node( index ); + if ( !node ) + return QList< QAction * >(); + + return node->actions( parent ); +} + +QModelIndex QgsAppQueryLogger::node2index( QgsDevToolsModelNode *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( QgsDevToolsModelNode *parentNode ) const +{ + Q_ASSERT( parentNode ); + + QgsDevToolsModelGroup *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 &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() ), QgsDevToolsModelNode::RoleId ).toInt(); + mQueryGroups.remove( popId ); + + beginRemoveRows( QModelIndex(), row, row ); + mRootNode->removeRow( row ); + endRemoveRows(); + } +} + +QgsDatabaseQueryLoggerRootNode *QgsAppQueryLogger::rootGroup() +{ + return mRootNode.get(); +} + +int QgsAppQueryLogger::rowCount( const QModelIndex &parent ) const +{ + QgsDevToolsModelNode *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(); + + QgsDevToolsModelGroup *n = dynamic_cast< QgsDevToolsModelGroup * >( 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 ( QgsDevToolsModelNode *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(); + + QgsDevToolsModelNode *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 +{ + if ( ! mFilterString.isEmpty() ) + { + QgsDevToolsModelNode *node = mLogger->index2node( mLogger->index( source_row, 0, source_parent ) ); + if ( QgsDatabaseQueryLoggerQueryGroup *request = dynamic_cast< QgsDatabaseQueryLoggerQueryGroup * >( node ) ) + { + if ( request->data().toString().contains( mFilterString, Qt::CaseInsensitive ) ) + { + return true; + } + for ( int i = 0; i < request->childCount(); i++ ) + { + if ( QgsDevToolsModelValueNode *valueNode = static_cast( request->childAt( i ) ); valueNode->value().contains( mFilterString, Qt::CaseInsensitive ) ) + { + return true; + } + } + return false; + } + } + return true; +} diff --git a/src/app/devtools/querylogger/qgsappquerylogger.h b/src/app/devtools/querylogger/qgsappquerylogger.h new file mode 100644 index 00000000000..4917ec2b85a --- /dev/null +++ b/src/app/devtools/querylogger/qgsappquerylogger.h @@ -0,0 +1,137 @@ +/*************************************************************************** + 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 +#include +#include +#include "qgsdbquerylog.h" +#include + +class QgsDevToolsModelNode; +class QgsDevToolsModelGroup; +class QgsDatabaseQueryLoggerRootNode; +class QgsDatabaseQueryLoggerQueryGroup; +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; + + // 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. + */ + QgsDevToolsModelNode *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: + + /** + * Clears all logged entries. + */ + void clear(); + + private slots: + void queryLogged( const QgsDatabaseQueryLogEntry &query ); + void queryFinished( const QgsDatabaseQueryLogEntry &query ); + + private: + + //! Returns index for a given node + QModelIndex node2index( QgsDevToolsModelNode *node ) const; + QModelIndex indexOfParentLayerTreeNode( QgsDevToolsModelNode *parentNode ) const; + + std::unique_ptr< QgsDatabaseQueryLoggerRootNode > mRootNode; + + QHash< int, QgsDatabaseQueryLoggerQueryGroup * > mQueryGroups; + +}; + +/** + * \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 diff --git a/src/app/devtools/querylogger/qgsdatabasequeryloggernode.cpp b/src/app/devtools/querylogger/qgsdatabasequeryloggernode.cpp new file mode 100644 index 00000000000..4ca9880306d --- /dev/null +++ b/src/app/devtools/querylogger/qgsdatabasequeryloggernode.cpp @@ -0,0 +1,251 @@ +/*************************************************************************** + 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 "qgsdatabasequeryloggernode.h" +#include "qgis.h" +#include "qgsjsonutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// +// QgsDatabaseQueryLoggerRootNode +// + +QgsDatabaseQueryLoggerRootNode::QgsDatabaseQueryLoggerRootNode() + : QgsDevToolsModelGroup( 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< QgsDevToolsModelNode > &child : mChildren ) + res << child->toVariant(); + return res; +} + + +// +// QgsDatabaseQueryLoggerQueryGroup +// + +QgsDatabaseQueryLoggerQueryGroup::QgsDatabaseQueryLoggerQueryGroup( const QgsDatabaseQueryLogEntry &query ) + : QgsDevToolsModelGroup( QString() ) + , mSql( query.query ) + , mQueryId( query.queryId ) +{ +#if 0 + std::unique_ptr< QgsNetworkLoggerRequestDetailsGroup > detailsGroup = std::make_unique< QgsNetworkLoggerRequestDetailsGroup >( request ); + mDetailsGroup = detailsGroup.get(); + addChild( std::move( detailsGroup ) ); +#endif + + addKeyValueNode( QObject::tr( "Provider" ), query.provider ); + addKeyValueNode( QObject::tr( "URI" ), query.uri ); + addKeyValueNode( QObject::tr( "Started at" ), QDateTime::fromMSecsSinceEpoch( query.startedTime ).toString( Qt::ISODateWithMs ) ); +#if 0 + addKeyValueNode( QObject::tr( "Thread" ), query.originatingThreadId() ); +#endif + addKeyValueNode( QObject::tr( "Initiator" ), query.initiatorClass.isEmpty() ? QObject::tr( "unknown" ) : query.initiatorClass ); + if ( !query.origin.isEmpty() ) + addKeyValueNode( QObject::tr( "Location" ), query.origin ); + +} + +QVariant QgsDatabaseQueryLoggerQueryGroup::data( int role ) const +{ + switch ( role ) + { + case Qt::DisplayRole: + return QStringLiteral( "%1 %2" ).arg( QString::number( mQueryId ), + mSql ); + + case Qt::ToolTipRole: + { + // Show no more than 255 characters + return mSql.length() > 255 ? mSql.mid( 0, 255 ).append( QStringLiteral( "…" ) ) : mSql; + +#if 0 + QString bytes = QObject::tr( "unknown" ); + if ( mBytesTotal != 0 ) + { + if ( mBytesReceived > 0 && mBytesReceived < mBytesTotal ) + bytes = QStringLiteral( "%1/%2" ).arg( QString::number( mBytesReceived ), QString::number( mBytesTotal ) ); + else if ( mBytesReceived > 0 && mBytesReceived == mBytesTotal ) + bytes = QString::number( mBytesTotal ); + } + // ?? adding
instead of \n after (very long) url seems to break url up + // COMPLETE, Status: 200 - text/xml; charset=utf-8 - 2334 bytes - 657 milliseconds + return QStringLiteral( "%1
%2 - Status: %3 - %4 - %5 bytes - %6 msec - %7 replies" ) + .arg( mUrl.url(), + statusToString( mStatus ), + QString::number( mHttpStatus ), + mContentType, + bytes, + mStatus == Status::Pending ? QString::number( mTimer.elapsed() / 1000 ) : QString::number( mTotalTime ), + QString::number( mReplies ) ); +#endif + } + + case RoleStatus: + return static_cast< int >( mStatus ); + + case RoleId: + return mQueryId; + + case Qt::ForegroundRole: + { + switch ( mStatus ) + { + case QgsDatabaseQueryLoggerQueryGroup::Status::Pending: + case QgsDatabaseQueryLoggerQueryGroup::Status::Canceled: + return QBrush( QColor( 0, 0, 0, 100 ) ); + case QgsDatabaseQueryLoggerQueryGroup::Status::Error: + return QBrush( QColor( 235, 10, 10 ) ); + case QgsDatabaseQueryLoggerQueryGroup::Status::TimeOut: + return QBrush( QColor( 235, 10, 10 ) ); + case QgsDatabaseQueryLoggerQueryGroup::Status::Complete: + break; + } + break; + } + + case Qt::FontRole: + { + if ( mStatus == Status::Canceled ) + { + QFont f; + f.setStrikeOut( true ); + return f; + } + break; + } + + default: + break; + } + return QVariant( ); +} + +QList QgsDatabaseQueryLoggerQueryGroup::actions( QObject *parent ) +{ + QList< QAction * > res; + + QAction *copyUrlAction = new QAction( QObject::tr( "Copy SQL" ), parent ); + QObject::connect( copyUrlAction, &QAction::triggered, copyUrlAction, [ = ] + { + QApplication::clipboard()->setText( mSql ); + } ); + res << copyUrlAction; + + QAction *copyJsonAction = new QAction( QObject::tr( "Copy as JSON" ), parent ); + QObject::connect( copyJsonAction, &QAction::triggered, copyJsonAction, [ = ] + { + const QVariant value = toVariant(); + const QString json = QString::fromStdString( QgsJsonUtils::jsonFromVariant( value ).dump( 2 ) ); + QApplication::clipboard()->setText( json ); + + } ); + res << copyJsonAction; + + return res; +} + +QVariant QgsDatabaseQueryLoggerQueryGroup::toVariant() const +{ + QVariantMap res; + res.insert( QStringLiteral( "SQL" ), mSql ); + + for ( const auto &child : std::as_const( mChildren ) ) + { + if ( const QgsDevToolsModelValueNode *valueNode = dynamic_cast< const QgsDevToolsModelValueNode *>( child.get() ) ) + { + res.insert( valueNode->key(), valueNode->value() ); + } + } + return res; +} + +void QgsDatabaseQueryLoggerQueryGroup::setFinished( const QgsDatabaseQueryLogEntry &query ) +{ + if ( query.error.isEmpty() ) + { + mStatus = query.canceled ? Status::Canceled : Status::Complete; + addKeyValueNode( QObject::tr( "Total time (ms)" ), QLocale().toString( query.finishedTime - query.startedTime ) ); + if ( query.fetchedRows != -1 ) + { + addKeyValueNode( QObject::tr( "Row count" ), QLocale().toString( query.fetchedRows ) ); + } + } + else + { + mStatus = Status::Error; + addKeyValueNode( QObject::tr( "Error" ), query.error ); + } +} + +void QgsDatabaseQueryLoggerQueryGroup::setStatus( QgsDatabaseQueryLoggerQueryGroup::Status status ) +{ + mStatus = status; +} + +QString QgsDatabaseQueryLoggerQueryGroup::statusToString( QgsDatabaseQueryLoggerQueryGroup::Status status ) +{ + switch ( status ) + { + case QgsDatabaseQueryLoggerQueryGroup::Status::Pending: + return QObject::tr( "Pending" ); + case QgsDatabaseQueryLoggerQueryGroup::Status::Complete: + return QObject::tr( "Complete" ); + case QgsDatabaseQueryLoggerQueryGroup::Status::Error: + return QObject::tr( "Error" ); + case QgsDatabaseQueryLoggerQueryGroup::Status::TimeOut: + return QObject::tr( "Timeout" ); + case QgsDatabaseQueryLoggerQueryGroup::Status::Canceled: + return QObject::tr( "Canceled" ); + } + return QString(); +} + +void QgsDatabaseQueryLoggerQueryGroup::setSql( const QString &sql ) +{ + mSql = sql; +} + +const QString &QgsDatabaseQueryLoggerQueryGroup::sql() const +{ + return mSql; +} + + diff --git a/src/app/devtools/querylogger/qgsdatabasequeryloggernode.h b/src/app/devtools/querylogger/qgsdatabasequeryloggernode.h new file mode 100644 index 00000000000..ee7f30d5e5d --- /dev/null +++ b/src/app/devtools/querylogger/qgsdatabasequeryloggernode.h @@ -0,0 +1,125 @@ +/*************************************************************************** + 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 QGSDBQUERYLOGGERNODE_H +#define QGSDBQUERYLOGGERNODE_H + +#include +#include +#include +#include +#include +#include +#include "devtools/qgsdevtoolsmodelnode.h" +#include "qgsdbquerylog.h" + +class QAction; + +/** + * \ingroup app + * \class QgsDatabaseQueryLoggerRootNode + * \brief Root node for the query logger model. + */ +class QgsDatabaseQueryLoggerRootNode final : public QgsDevToolsModelGroup +{ + 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; +}; + + +/** + * \ingroup app + * \class QgsDatabaseQueryLoggerQueryGroup + * \brief Parent group for all database queries, showing the query id, SQL + * and containing child groups with detailed query and result information. + * + * Visually, a QgsDatabaseQueryLoggerQueryGroup is structured by: + * + * |__ QgsDatabaseQueryLoggerQueryGroup (showing sql, uri,...) + * |__ QgsDevToolsModelValueNode(key-value pairs with info) + * ... + */ +class QgsDatabaseQueryLoggerQueryGroup final : public QgsDevToolsModelGroup +{ + public: + + //! Query status + enum class Status + { + Pending, //!< Query underway + Complete, //!< Query was successfully completed + Error, //!< Query encountered an error + TimeOut, //!< Query timed out + Canceled, //!< Query was manually canceled + }; + + /** + * Constructor for QgsDatabaseQueryLoggerQueryGroup, populated from the + * specified \a query details. + */ + QgsDatabaseQueryLoggerQueryGroup( const QgsDatabaseQueryLogEntry &query ); + QVariant data( int role = Qt::DisplayRole ) const override; + QList< QAction * > actions( QObject *parent ) override final; + QVariant toVariant() const override; + + /** + * Called when the \a query is finished. + * + * Will automatically create children encapsulating the completed details. + */ + void setFinished( const QgsDatabaseQueryLogEntry &query ); + + /** + * Returns the query's status. + */ + Status status() const { return mStatus; } + + /** + * Set the query \a status + */ + void setStatus( Status status ); + + /** + * Converts a request \a status to a translated string value. + */ + static QString statusToString( Status status ); + + /** + * Sets the SQL to \a sql. + */ + void setSql( const QString &sql ); + + /** + * Returns the group SQL. + */ + const QString &sql() const; + + private: + + QString mSql; + int mQueryId = 0; + QByteArray mData; + Status mStatus = Status::Pending; +}; + +#endif // QGSDBQUERYLOGGERNODE_H diff --git a/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.cpp b/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.cpp new file mode 100644 index 00000000000..dceafff84dd --- /dev/null +++ b/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.cpp @@ -0,0 +1,205 @@ +/*************************************************************************** + qgsqueryloggerpanelwidget.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 "qgsdatabasequeryloggernode.h" +#include "qgsappquerylogger.h" +#include "qgssettings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// +// 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 queries" ) ); + + mActionRecord->setChecked( QgsApplication::databaseQueryLog()->enabled() ); + + connect( mFilterLineEdit, &QgsFilterLineEdit::textChanged, mTreeView, &QgsDatabaseQueryLoggerTreeView::setFilterString ); + connect( mActionClear, &QAction::triggered, mLogger, &QgsAppQueryLogger::clear ); + connect( mActionRecord, &QAction::toggled, this, [ = ]( bool enabled ) + { + QgsSettings().setValue( QStringLiteral( "logDatabaseQueries" ), enabled, QgsSettings::App ); + QgsApplication::databaseQueryLog()->setEnabled( enabled ); + } ); + connect( mActionSaveLog, &QAction::triggered, this, [ = ]() + { + if ( QMessageBox::warning( this, tr( "Save Database Query Log" ), + tr( "Security warning: query 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 Query 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 ); +} diff --git a/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.h b/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.h new file mode 100644 index 00000000000..b73681e6576 --- /dev/null +++ b/src/app/devtools/querylogger/qgsqueryloggerpanelwidget.h @@ -0,0 +1,84 @@ +/*************************************************************************** + qgsqueryloggerpanelwidget.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 QGSQUERYLOGGERPANELWIDGET_H +#define QGSQUERYLOGGERPANELWIDGET_H + +#include "qgsdevtoolwidget.h" +#include "ui_qgsqueryloggerpanelbase.h" +#include + +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 // QGSQUERYLOGGERPANELWIDGET_H diff --git a/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.cpp b/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.cpp new file mode 100644 index 00000000000..c34f898849f --- /dev/null +++ b/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.cpp @@ -0,0 +1,29 @@ +/*************************************************************************** + qgsqueryloggerwidgetfactory.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/database.svg" ) ) ) + , mLogger( logger ) +{ +} + +QgsDevToolWidget *QgsDatabaseQueryLoggerWidgetFactory::createWidget( QWidget *parent ) const +{ + return new QgsDatabaseQueryLoggerPanelWidget( mLogger, parent ); +} diff --git a/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.h b/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.h new file mode 100644 index 00000000000..fcb22810fd3 --- /dev/null +++ b/src/app/devtools/querylogger/qgsqueryloggerwidgetfactory.h @@ -0,0 +1,35 @@ +/*************************************************************************** + qgsqueryloggerwidgetfactory.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 QGSQUERYLOGGERWIDGETFACTORY_H +#define QGSQUERYLOGGERWIDGETFACTORY_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 // QGSQUERYLOGGERWIDGETFACTORY_H diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 8ec59427cf0..e9f9149bfc6 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -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" @@ -1034,6 +1036,10 @@ QgisApp::QgisApp( QSplashScreen *splash, bool restorePlugins, bool skipBadLayers QgsSettings settings; + startProfile( tr( "Create database query logger" ) ); + mQueryLogger = new QgsAppQueryLogger( this ); + QgsApplication::databaseQueryLog()->setEnabled( settings.value( QStringLiteral( "logDatabaseQueries" ), false, QgsSettings::App ).toBool() ); + endProfile(); startProfile( tr( "Building style sheet" ) ); // set up stylesheet builder and apply saved or default style options @@ -1831,6 +1837,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(); diff --git a/src/app/qgisapp.h b/src/app/qgisapp.h index a623f2c8acd..3ada37a8afa 100644 --- a/src/app/qgisapp.h +++ b/src/app/qgisapp.h @@ -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; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4a7dd9d7e6f..3b79736a79e 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -356,6 +356,7 @@ set(QGIS_CORE_SRCS qgsdataprovidertemporalcapabilities.cpp qgsdatetimestatisticalsummary.cpp qgsdbfilterproxymodel.cpp + qgsdbquerylog.cpp qgsdefaultvalue.cpp qgsdiagramrenderer.cpp qgsdistancearea.cpp @@ -1005,6 +1006,7 @@ set(QGIS_CORE_HDRS qgsdatasourceuri.h qgsdatetimestatisticalsummary.h qgsdbfilterproxymodel.h + qgsdbquerylog.h qgsdefaultvalue.h qgsdiagramrenderer.h qgsdistancearea.h diff --git a/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp b/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp index c90ae7d5d32..051ee11cf21 100644 --- a/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp +++ b/src/core/providers/ogr/qgsgeopackageproviderconnection.cpp @@ -28,6 +28,7 @@ #include "qgsfeedback.h" #include "qgsogrutils.h" #include "qgsfielddomain.h" +#include "qgsdbquerylog.h" #include #include @@ -351,8 +352,11 @@ void QgsGeoPackageProviderConnection::setDefaultCapabilities() QgsAbstractDatabaseProviderConnection::QueryResult QgsGeoPackageProviderConnection::executeGdalSqlPrivate( const QString &sql, QgsFeedback *feedback ) const { + QgsDatabaseQueryLogWrapper logWrapper( sql, uri(), providerKey(), QStringLiteral( "QgsGeoPackageProviderConnection" ), QGS_QUERY_LOG_ORIGIN ); + if ( feedback && feedback->isCanceled() ) { + logWrapper.setCanceled(); return QgsAbstractDatabaseProviderConnection::QueryResult(); } @@ -363,6 +367,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsGeoPackageProviderConnecti if ( feedback && feedback->isCanceled() ) { + logWrapper.setCanceled(); return QgsAbstractDatabaseProviderConnection::QueryResult(); } @@ -379,9 +384,9 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsGeoPackageProviderConnecti results.setQueryExecutionTime( std::chrono::duration_cast( end - begin ).count() ); gdal::ogr_feature_unique_ptr fet; + if ( fet.reset( OGR_L_GetNextFeature( ogrLayer ) ), fet ) { - // pk column name QString pkColumnName; @@ -453,11 +458,13 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsGeoPackageProviderConnecti if ( ! errCause.isEmpty() ) { + logWrapper.setError( errCause ); throw QgsProviderConnectionException( QObject::tr( "Error executing SQL statement %1: %2" ).arg( sql, errCause ) ); } OGR_L_ResetReading( ogrLayer ); iterator->nextRow(); + return results; } @@ -475,6 +482,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsGeoPackageProviderConnecti if ( !errCause.isEmpty() ) { + logWrapper.setError( errCause ); throw QgsProviderConnectionException( QObject::tr( "Error executing SQL %1: %2" ).arg( sql, errCause ) ); } diff --git a/src/core/qgsapplication.cpp b/src/core/qgsapplication.cpp index 9164a1f1e2c..b2c5032a2dc 100644 --- a/src/core/qgsapplication.cpp +++ b/src/core/qgsapplication.cpp @@ -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" ); + qRegisterMetaType( "QgsDatabaseQueryLogEntry" ); qRegisterMetaType( "QgsProcessingFeatureSourceDefinition" ); qRegisterMetaType( "QgsProcessingOutputLayerDefinition" ); qRegisterMetaType( "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; } diff --git a/src/core/qgsapplication.h b/src/core/qgsapplication.h index 040dcc2462c..efed5c9d70a 100644 --- a/src/core/qgsapplication.h +++ b/src/core/qgsapplication.h @@ -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; diff --git a/src/core/qgsdbquerylog.cpp b/src/core/qgsdbquerylog.cpp new file mode 100644 index 00000000000..f19107151e9 --- /dev/null +++ b/src/core/qgsdbquerylog.cpp @@ -0,0 +1,76 @@ +/*************************************************************************** + 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 + +// +// QgsDatabaseQueryLogEntry +// + +QAtomicInt QgsDatabaseQueryLogEntry::sQueryId = 0; + +QgsDatabaseQueryLogEntry::QgsDatabaseQueryLogEntry( const QString &query ) + : queryId( ++sQueryId ) + , query( query ) + , startedTime( QDateTime::currentMSecsSinceEpoch() ) +{} + + +// +// QgsDatabaseQueryLog +// + +bool QgsDatabaseQueryLog::sEnabled = false; + +QgsDatabaseQueryLog::QgsDatabaseQueryLog( QObject *parent ) + : QObject( parent ) +{ + +} + +void QgsDatabaseQueryLog::log( const QgsDatabaseQueryLogEntry &query ) +{ + if ( !sEnabled ) + return; + + QMetaObject::invokeMethod( QgsApplication::databaseQueryLog(), "queryStartedPrivate", Qt::QueuedConnection, Q_ARG( QgsDatabaseQueryLogEntry, query ) ); +} + +void QgsDatabaseQueryLog::finished( const QgsDatabaseQueryLogEntry &query ) +{ + if ( !sEnabled ) + return; + + // 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 ); +} + diff --git a/src/core/qgsdbquerylog.h b/src/core/qgsdbquerylog.h new file mode 100644 index 00000000000..e4eb6b0cb52 --- /dev/null +++ b/src/core/qgsdbquerylog.h @@ -0,0 +1,270 @@ +/*************************************************************************** + 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 +#include + +/** + * \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; + + //! Database URI + QString uri; + + //! Provider key + QString provider; + + //! 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; + + /** + * Number of fetched/affected rows. + * \warning Not all providers support this information. + */ + long long fetchedRows = -1; + + /** + * Error reported by the provider, normally blank + */ + QString error; + + /** + * Canceled flag for user canceled queries. + */ + bool canceled = false; + + 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__ + ")"); +#define QGS_QUERY_LOG_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 ); + + /** + * Enables query logging. + * + * If disabled, no signals will be emitted by the log. By default the log is disabled, + * and clients must manually enable it. + * + * \note Not available in Python bindings + * \see enabled() + */ + static void setEnabled( bool enabled ) SIP_SKIP { sEnabled = enabled; } + + /** + * Returns TRUE if logging is enabled. + * + * \see setEnabled() + */ + static bool enabled() { return sEnabled; } + + /** + * 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 has 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; + + private: + + static bool sEnabled; + +}; + +#ifndef SIP_RUN +///@cond private + +/** + * The QgsDatabaseQueryLogWrapper class is a RIIA wrapper for the query logger. + */ +class QgsDatabaseQueryLogWrapper +{ + + public: + + QgsDatabaseQueryLogWrapper( const QString &query, const QString &uri, const QString &provider, const QString &initiatorClass, const QString &origin ) + : mEntry( query ) + { + mEntry.uri = uri; + mEntry.origin = origin; + mEntry.initiatorClass = initiatorClass; + mEntry.provider = provider; + QgsDatabaseQueryLog::log( mEntry ); + } + + ~QgsDatabaseQueryLogWrapper( ) + { + QgsDatabaseQueryLog::finished( mEntry ); + } + + void setFetchedRows( long long fetchedRows ) + { + mEntry.fetchedRows = fetchedRows; + } + + void setQuery( const QString &query ) + { + mEntry.query = query; + } + + void setError( const QString &error ) + { + mEntry.error = error; + } + + void setCanceled( ) + { + mEntry.canceled = true; + } + + private: + + QgsDatabaseQueryLogEntry mEntry; + +}; + +///@endcond +#endif + +#endif // QGSDBQUERYLOG_H diff --git a/src/providers/mssql/qgsmssqlfeatureiterator.cpp b/src/providers/mssql/qgsmssqlfeatureiterator.cpp index aef66f15a7b..626afb43785 100644 --- a/src/providers/mssql/qgsmssqlfeatureiterator.cpp +++ b/src/providers/mssql/qgsmssqlfeatureiterator.cpp @@ -20,6 +20,7 @@ #include "qgsmssqlprovider.h" #include "qgsmssqltransaction.h" #include "qgslogger.h" +#include "qgsdbquerylog.h" #include "qgssettings.h" #include "qgsexception.h" #include "qgsmssqldatabase.h" @@ -525,7 +526,7 @@ bool QgsMssqlFeatureIterator::fetchFeature( QgsFeature &feature ) case PktFidMap: { QVariantList primaryKeyVals; - for ( int idx : mSource->mPrimaryKeyAttrs ) + for ( int idx : std::as_const( mSource->mPrimaryKeyAttrs ) ) { QgsField fld = mSource->mFields.at( idx ); @@ -607,29 +608,50 @@ bool QgsMssqlFeatureIterator::rewind() mQuery->clear(); mQuery->setForwardOnly( true ); - bool result = mQuery->exec( mOrderByClause.isEmpty() ? mStatement : mStatement + mOrderByClause ); - if ( !result && !mFallbackStatement.isEmpty() ) + QString sql { mOrderByClause.isEmpty() ? mStatement : mStatement + mOrderByClause }; + std::unique_ptr logWrapper = std::make_unique( sql, mSource->connInfo(), QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ); + + bool result = mQuery->exec( sql ); + if ( !result ) { - //try with fallback statement - result = mQuery->exec( mOrderByClause.isEmpty() ? mFallbackStatement : mFallbackStatement + mOrderByClause ); - if ( result ) + logWrapper->setError( mQuery->lastError().text() ); + if ( !mFallbackStatement.isEmpty() ) { - mExpressionCompiled = false; - mCompileStatus = NoCompilation; + //try with fallback statement + sql = mOrderByClause.isEmpty() ? mFallbackStatement : mFallbackStatement + mOrderByClause; + logWrapper.reset( new QgsDatabaseQueryLogWrapper( sql, mSource->connInfo(), QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ) ); + result = mQuery->exec( sql ); + if ( result ) + { + mExpressionCompiled = false; + mCompileStatus = NoCompilation; + } + else + { + logWrapper->setError( mQuery->lastError().text() ); + } } } if ( !result && !mOrderByClause.isEmpty() ) { //try without order by clause + logWrapper.reset( new QgsDatabaseQueryLogWrapper( mStatement, mSource->connInfo(), QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ) ); result = mQuery->exec( mStatement ); if ( result ) + { mOrderByCompiled = false; + } + else + { + logWrapper->setError( mQuery->lastError().text() ); + } } if ( !result && !mFallbackStatement.isEmpty() && !mOrderByClause.isEmpty() ) { //try with fallback statement and without order by clause + logWrapper.reset( new QgsDatabaseQueryLogWrapper( mFallbackStatement, mSource->connInfo(), QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ) ); result = mQuery->exec( mFallbackStatement ); if ( result ) { @@ -637,6 +659,10 @@ bool QgsMssqlFeatureIterator::rewind() mOrderByCompiled = false; mCompileStatus = NoCompilation; } + else + { + logWrapper->setError( mQuery->lastError().text() ); + } } if ( !result ) @@ -689,9 +715,15 @@ QgsMssqlFeatureSource::QgsMssqlFeatureSource( const QgsMssqlProvider *p ) , mDisableInvalidGeometryHandling( p->mDisableInvalidGeometryHandling ) , mCrs( p->crs() ) , mTransactionConn( p->transaction() ? static_cast( p->transaction() )->conn() : std::shared_ptr() ) + , mConnInfo( p->uri().uri( ) ) {} QgsFeatureIterator QgsMssqlFeatureSource::getFeatures( const QgsFeatureRequest &request ) { return QgsFeatureIterator( new QgsMssqlFeatureIterator( this, false, request ) ); } + +const QString &QgsMssqlFeatureSource::connInfo() const +{ + return mConnInfo; +} diff --git a/src/providers/mssql/qgsmssqlfeatureiterator.h b/src/providers/mssql/qgsmssqlfeatureiterator.h index f16cbfc4ec8..92d54152af4 100644 --- a/src/providers/mssql/qgsmssqlfeatureiterator.h +++ b/src/providers/mssql/qgsmssqlfeatureiterator.h @@ -38,6 +38,8 @@ class QgsMssqlFeatureSource final: public QgsAbstractFeatureSource QgsFeatureIterator getFeatures( const QgsFeatureRequest &request ) override; + const QString &connInfo() const; + private: QgsFields mFields; QgsMssqlPrimaryKeyType mPrimaryKeyType; @@ -76,6 +78,9 @@ class QgsMssqlFeatureSource final: public QgsAbstractFeatureSource // Return True if this feature source has spatial attributes. bool isSpatial() { return !mGeometryColName.isEmpty() || !mGeometryColType.isEmpty(); } + // Uri information for query logger + QString mConnInfo; + friend class QgsMssqlFeatureIterator; friend class QgsMssqlExpressionCompiler; }; diff --git a/src/providers/mssql/qgsmssqlprovider.cpp b/src/providers/mssql/qgsmssqlprovider.cpp index 5a3b88528f8..83cd2bbab83 100644 --- a/src/providers/mssql/qgsmssqlprovider.cpp +++ b/src/providers/mssql/qgsmssqlprovider.cpp @@ -20,6 +20,7 @@ #include "qgsmssqldatabase.h" #include "qgsmssqlproviderconnection.h" #include "qgsfeedback.h" +#include "qgsdbquerylog.h" #include #include @@ -53,6 +54,12 @@ #include "qgsmssqltransaction.h" +#include "qgsconfig.h" +constexpr int sMssqlConQueryLogFilePrefixLength = CMAKE_SOURCE_DIR[sizeof( CMAKE_SOURCE_DIR ) - 1] == '/' ? sizeof( CMAKE_SOURCE_DIR ) + 1 : sizeof( CMAKE_SOURCE_DIR ); +#define LoggedExec(query, sql ) execLogged( query, sql, QString(QString( __FILE__ ).mid( sMssqlConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) +#define LoggedExecMetadata(query, sql, uri ) execLogged( query, sql, uri, QString(QString( __FILE__ ).mid( sMssqlConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) + + const QString QgsMssqlProvider::MSSQL_PROVIDER_KEY = QStringLiteral( "mssql" ); const QString QgsMssqlProvider::MSSQL_PROVIDER_DESCRIPTION = QStringLiteral( "MSSQL spatial data provider" ); int QgsMssqlProvider::sConnectionId = 0; @@ -314,7 +321,7 @@ void QgsMssqlProvider::loadMetadata() QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "SELECT f_geometry_column, srid, geometry_type, coord_dimension FROM geometry_columns WHERE f_table_schema=%1 AND f_table_name=%2" ).arg( quotedValue( mSchemaName ), quotedValue( mTableName ) ) ) ) + if ( !LoggedExec( query, QStringLiteral( "SELECT f_geometry_column, srid, geometry_type, coord_dimension FROM geometry_columns WHERE f_table_schema=%1 AND f_table_name=%2" ).arg( quotedValue( mSchemaName ), quotedValue( mTableName ) ) ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -333,6 +340,29 @@ void QgsMssqlProvider::loadMetadata() } } +bool QgsMssqlProvider::execLogged( QSqlQuery &qry, const QString &sql, const QString &queryOrigin ) const +{ + QgsDatabaseQueryLogWrapper logWrapper{ sql, uri().uri(), QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), queryOrigin }; + const bool res { qry.exec( sql ) }; + if ( ! res ) + { + logWrapper.setError( qry.lastError().text() ); + } + else + { + if ( qry.isSelect() ) + { + logWrapper.setFetchedRows( qry.size() ); + } + else + { + logWrapper.setFetchedRows( qry.numRowsAffected() ); + } + } + logWrapper.setQuery( qry.lastQuery() ); + return res; +} + void QgsMssqlProvider::setLastError( const QString &error ) { appendError( error ); @@ -361,8 +391,10 @@ void QgsMssqlProvider::loadFields() QSqlQuery query = createQuery(); query.setForwardOnly( true ); + const QString sql { QStringLiteral( "SELECT name FROM sys.columns WHERE is_computed = 1 AND object_id = OBJECT_ID('[%1].[%2]')" ).arg( mSchemaName, mTableName ) }; + // Get computed columns which need to be ignored on insert or update. - if ( !query.exec( QStringLiteral( "SELECT name FROM sys.columns WHERE is_computed = 1 AND object_id = OBJECT_ID('[%1].[%2]')" ).arg( mSchemaName, mTableName ) ) ) + if ( !LoggedExec( query, sql ) ) { pushError( query.lastError().text() ); return; @@ -376,20 +408,24 @@ void QgsMssqlProvider::loadFields() // Field has unique constraint QSet setColumnUnique; { - if ( !query.exec( QStringLiteral( "SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC" - " INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CC ON TC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME" - " WHERE TC.CONSTRAINT_SCHEMA = '%1' AND TC.TABLE_NAME = '%2' AND TC.CONSTRAINT_TYPE = 'unique'" ) - .arg( mSchemaName, mTableName ) ) ) + const QString sql2 { QStringLiteral( "SELECT * FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC" + " INNER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE CC ON TC.CONSTRAINT_NAME = CC.CONSTRAINT_NAME" + " WHERE TC.CONSTRAINT_SCHEMA = '%1' AND TC.TABLE_NAME = '%2' AND TC.CONSTRAINT_TYPE = 'unique'" ) + .arg( mSchemaName, mTableName ) }; + if ( !LoggedExec( query, sql2 ) ) { pushError( query.lastError().text() ); return; } while ( query.next() ) + { setColumnUnique.insert( query.value( QStringLiteral( "COLUMN_NAME" ) ).toString() ); + } } - if ( !query.exec( QStringLiteral( "exec sp_columns @table_name = N%1, @table_owner = %2" ).arg( quotedValue( mTableName ), quotedValue( mSchemaName ) ) ) ) + const QString sql3 { QStringLiteral( "exec sp_columns @table_name = N%1, @table_owner = %2" ).arg( quotedValue( mTableName ), quotedValue( mSchemaName ) ) }; + if ( !LoggedExec( query, sql3 ) ) { pushError( query.lastError().text() ); return; @@ -504,7 +540,8 @@ void QgsMssqlProvider::loadFields() { query.clear(); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "exec sp_pkeys @table_name = N%1, @table_owner = %2 " ).arg( quotedValue( mTableName ), quotedValue( mSchemaName ) ) ) ) + const QString sql4 { QStringLiteral( "exec sp_pkeys @table_name = N%1, @table_owner = %2 " ).arg( quotedValue( mTableName ), quotedValue( mSchemaName ) ) }; + if ( !LoggedExec( query, sql4 ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -542,8 +579,9 @@ void QgsMssqlProvider::loadFields() { query.clear(); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "select count(distinct [%1]), count([%1]) from [%2].[%3]" ) - .arg( pk, mSchemaName, mTableName ) ) ) + const QString sql5 { QStringLiteral( "select count(distinct [%1]), count([%1]) from [%2].[%3]" ) + .arg( pk, mSchemaName, mTableName ) }; + if ( !LoggedExec( query, sql5 ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -636,7 +674,7 @@ QVariant QgsMssqlProvider::defaultValue( int fieldId ) const QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { const QString errorMessage( tr( "Could not execute query: %1" ).arg( query.lastError().text() ) ); QgsDebugMsg( errorMessage ); @@ -704,7 +742,7 @@ QVariant QgsMssqlProvider::minimumValue( int index ) const QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -745,7 +783,7 @@ QVariant QgsMssqlProvider::maximumValue( int index ) const QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -794,7 +832,7 @@ QSet QgsMssqlProvider::uniqueValues( int index, int limit ) const QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -848,7 +886,7 @@ QStringList QgsMssqlProvider::uniqueStringsMatching( int index, const QString &s QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -896,7 +934,7 @@ void QgsMssqlProvider::UpdateStatistics( bool estimate ) const statement = QString( sql ).arg( mSchemaName, mTableName ); - if ( query.exec( statement ) ) + if ( LoggedExec( query, statement ) ) { if ( query.next() && ( !query.value( 0 ).isNull() || !query.value( 1 ).isNull() || @@ -984,7 +1022,7 @@ void QgsMssqlProvider::UpdateStatistics( bool estimate ) const const QString statementSample = statement + ( mSqlWhereClause.isEmpty() ? " WHERE " : " AND " ) + sampleFilter; - if ( query.exec( statementSample ) && query.next() && + if ( LoggedExec( query, statementSample ) && query.next() && !query.value( 0 ).isNull() && query.value( 4 ).toInt() >= minSampleCount ) { mExtent.setXMinimum( query.value( 0 ).toDouble() ); @@ -995,7 +1033,7 @@ void QgsMssqlProvider::UpdateStatistics( bool estimate ) const } } - if ( !query.exec( statement ) ) + if ( !LoggedExec( query, statement ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); } @@ -1074,7 +1112,7 @@ long long QgsMssqlProvider::featureCount() const " JOIN sys.partitions p ON t.object_id = p.object_id AND p.index_id IN (0,1)" " WHERE SCHEMA_NAME(t.schema_id) = %1 AND OBJECT_NAME(t.OBJECT_ID) = %2" ).arg( quotedValue( mSchemaName ), quotedValue( mTableName ) ); - if ( query.exec( statement ) && query.next() ) + if ( LoggedExec( query, statement ) && query.next() ) { return query.value( 0 ).toLongLong(); } @@ -1432,7 +1470,7 @@ bool QgsMssqlProvider::addAttributes( const QList &attributes ) QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( statement ) ) + if ( !LoggedExec( query, statement ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); return false; @@ -1465,13 +1503,14 @@ bool QgsMssqlProvider::deleteAttributes( const QgsAttributeIds &attributes ) QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( statement ) ) + if ( !LoggedExec( query, statement ) ) { QgsDebugMsg( QStringLiteral( "SQL:%1\n Error:%2" ).arg( query.lastQuery(), query.lastError().text() ) ); return false; } query.finish(); + loadFields(); if ( mTransaction ) @@ -1732,7 +1771,8 @@ bool QgsMssqlProvider::deleteFeatures( const QgsFeatureIds &ids ) query.setForwardOnly( true ); const QString statement = QStringLiteral( "DELETE FROM [%1].[%2] WHERE [%3] IN (%4)" ).arg( mSchemaName, mTableName, mAttributeFields.at( mPrimaryKeyAttrs[0] ).name(), featureIds ); - if ( query.exec( statement ) ) + + if ( LoggedExec( query, statement ) ) { if ( query.numRowsAffected() == ids.size() ) { @@ -1744,7 +1784,9 @@ bool QgsMssqlProvider::deleteFeatures( const QgsFeatureIds &ids ) pushError( tr( "Only %1 of %2 features deleted" ).arg( query.numRowsAffected() ).arg( ids.size() ) ); } else + { pushError( query.lastError().text() ); + } } else if ( mPrimaryKeyType == PktFidMap ) { @@ -1754,7 +1796,8 @@ bool QgsMssqlProvider::deleteFeatures( const QgsFeatureIds &ids ) for ( QgsFeatureIds::const_iterator it = ids.begin(); it != ids.end(); ++it ) { const QString statement = QStringLiteral( "DELETE FROM [%1].[%2] WHERE %3" ).arg( mSchemaName, mTableName, whereClauseFid( *it ) ); - if ( query.exec( statement ) ) + + if ( LoggedExec( query, statement ) ) { if ( query.numRowsAffected() == 1 ) { @@ -1830,7 +1873,7 @@ bool QgsMssqlProvider::createSpatialIndex() statement += QLatin1String( " USING GEOGRAPHY_GRID" ); } - if ( !query.exec( statement ) ) + if ( !LoggedExec( query, statement ) ) { pushError( query.lastError().text() ); return false; @@ -1854,7 +1897,7 @@ bool QgsMssqlProvider::createAttributeIndex( int field ) statement = QStringLiteral( "CREATE NONCLUSTERED INDEX [qgs_%1_idx] ON [%2].[%3] ( [%4] )" ).arg( mGeometryColName, mSchemaName, mTableName, mAttributeFields.at( field ).name() ); - if ( !query.exec( statement ) ) + if ( !LoggedExec( query, statement ) ) { pushError( query.lastError().text() ); return false; @@ -1870,7 +1913,9 @@ QgsCoordinateReferenceSystem QgsMssqlProvider::crs() const // try to load crs from the database tables as a fallback QSqlQuery query = createQuery(); query.setForwardOnly( true ); - bool execOk = query.exec( QStringLiteral( "SELECT srtext FROM spatial_ref_sys WHERE srid=%1" ).arg( mSRId ) ); + const QString statement { QStringLiteral( "SELECT srtext FROM spatial_ref_sys WHERE srid=%1" ).arg( mSRId ) }; + + bool execOk = LoggedExec( query, statement ); if ( execOk && query.isActive() ) { if ( query.next() ) @@ -1886,7 +1931,7 @@ QgsCoordinateReferenceSystem QgsMssqlProvider::crs() const query.setForwardOnly( true ); // Look in the system reference table for the data if we can't find it yet - execOk = query.exec( QStringLiteral( "SELECT well_known_text FROM sys.spatial_reference_systems WHERE spatial_reference_id=%1" ).arg( mSRId ) ); + execOk = LoggedExec( query, QStringLiteral( "SELECT well_known_text FROM sys.spatial_reference_systems WHERE spatial_reference_id=%1" ).arg( mSRId ) ); if ( execOk && query.isActive() && query.next() ) { mCrs = QgsCoordinateReferenceSystem::fromWkt( query.value( 0 ).toString() ); @@ -1949,7 +1994,7 @@ bool QgsMssqlProvider::setSubsetString( const QString &theSQL, bool ) QSqlQuery query = createQuery(); query.setForwardOnly( true ); - if ( !query.exec( sql ) ) + if ( !LoggedExec( query, sql ) ) { pushError( query.lastError().text() ); mSqlWhereClause = prevWhere; @@ -2202,8 +2247,12 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, "WHERE object_id = OBJECT_ID(N'[dbo].[spatial_ref_sys]') AND type in (N'U')) " "CREATE TABLE spatial_ref_sys (srid integer not null " "PRIMARY KEY, auth_name varchar(256), auth_srid integer, srtext varchar(2048), proj4text varchar(2048))" ); + + std::unique_ptr logWrapper = std::make_unique( sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), QGS_QUERY_LOG_ORIGIN ); + if ( !q.exec( sql ) ) { + logWrapper->setError( q.lastError().text() ); if ( errorMessage ) *errorMessage = q.lastError().text(); return Qgis::VectorExportResult::ErrorCreatingLayer; @@ -2228,8 +2277,12 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, auth_srid, quotedValue( srs.toWkt() ), quotedValue( srs.toProj() ) ); + + logWrapper.reset( new QgsDatabaseQueryLogWrapper( sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), QGS_QUERY_LOG_ORIGIN ) ); + if ( !q.exec( sql ) ) { + logWrapper->setError( q.lastError().text() ); if ( errorMessage ) *errorMessage = q.lastError().text(); return Qgis::VectorExportResult::ErrorCreatingLayer; @@ -2246,8 +2299,12 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, // remove the old table with the same name sql = QStringLiteral( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) BEGIN DROP TABLE [%1].[%2] DELETE FROM geometry_columns where f_table_schema='%1' and f_table_name='%2' END;" ) .arg( schemaName, tableName ); + + logWrapper.reset( new QgsDatabaseQueryLogWrapper( sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), QGS_QUERY_LOG_ORIGIN ) ); + if ( !q.exec( sql ) ) { + logWrapper->setError( q.lastError().text() ); if ( errorMessage ) *errorMessage = q.lastError().text(); return Qgis::VectorExportResult::ErrorCreatingLayer; @@ -2258,8 +2315,12 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, // test for existing sql = QStringLiteral( "SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')" ) .arg( schemaName, tableName ); + + logWrapper.reset( new QgsDatabaseQueryLogWrapper( sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), QGS_QUERY_LOG_ORIGIN ) ); + if ( !q.exec( sql ) ) { + logWrapper->setError( q.lastError().text() ); if ( errorMessage ) *errorMessage = q.lastError().text(); return Qgis::VectorExportResult::ErrorCreatingLayer; @@ -2276,11 +2337,11 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, if ( !geometryColumn.isEmpty() ) { - sql = QString( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) DROP TABLE [%1].[%2]\n" - "CREATE TABLE [%1].[%2]([%3] [int] IDENTITY(1,1) NOT NULL, [%4] [geometry] NULL CONSTRAINT [PK_%2] PRIMARY KEY CLUSTERED ( [%3] ASC ))\n" - "DELETE FROM geometry_columns WHERE f_table_schema = '%1' AND f_table_name = '%2'\n" - "INSERT INTO [geometry_columns] ([f_table_catalog], [f_table_schema],[f_table_name], " - "[f_geometry_column],[coord_dimension],[srid],[geometry_type]) VALUES ('%5', '%1', '%2', '%4', %6, %7, '%8')" ) + sql = QStringLiteral( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) DROP TABLE [%1].[%2]\n" + "CREATE TABLE [%1].[%2]([%3] [int] IDENTITY(1,1) NOT NULL, [%4] [geometry] NULL CONSTRAINT [PK_%2] PRIMARY KEY CLUSTERED ( [%3] ASC ))\n" + "DELETE FROM geometry_columns WHERE f_table_schema = '%1' AND f_table_name = '%2'\n" + "INSERT INTO [geometry_columns] ([f_table_catalog], [f_table_schema],[f_table_name], " + "[f_geometry_column],[coord_dimension],[srid],[geometry_type]) VALUES ('%5', '%1', '%2', '%4', %6, %7, '%8')" ) .arg( schemaName, tableName, primaryKey, @@ -2293,17 +2354,20 @@ Qgis::VectorExportResult QgsMssqlProvider::createEmptyLayer( const QString &uri, else { //geometryless table - sql = QString( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) DROP TABLE [%1].[%2]\n" - "CREATE TABLE [%1].[%2]([%3] [int] IDENTITY(1,1) NOT NULL CONSTRAINT [PK_%2] PRIMARY KEY CLUSTERED ( [%3] ASC ))\n" - "DELETE FROM geometry_columns WHERE f_table_schema = '%1' AND f_table_name = '%2'\n" - ) + sql = QStringLiteral( "IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[%1].[%2]') AND type in (N'U')) DROP TABLE [%1].[%2]\n" + "CREATE TABLE [%1].[%2]([%3] [int] IDENTITY(1,1) NOT NULL CONSTRAINT [PK_%2] PRIMARY KEY CLUSTERED ( [%3] ASC ))\n" + "DELETE FROM geometry_columns WHERE f_table_schema = '%1' AND f_table_name = '%2'\n" + ) .arg( schemaName, tableName, primaryKey ); } + logWrapper.reset( new QgsDatabaseQueryLogWrapper( sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProvider" ), QGS_QUERY_LOG_ORIGIN ) ); + if ( !q.exec( sql ) ) { + logWrapper->setError( q.lastError().text() ); if ( errorMessage ) *errorMessage = q.lastError().text(); return Qgis::VectorExportResult::ErrorCreatingLayer; @@ -2462,7 +2526,9 @@ bool QgsMssqlProviderMetadata::styleExists( const QString &uri, const QString &s QSqlQuery query = QSqlQuery( db->db() ); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'layer_styles'" ) ) ) + const QString sql { QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'layer_styles'" ) }; + + if ( !LoggedExecMetadata( query, sql, uri ) ) { errorCause = QObject::tr( "Could not check if layer_styles table exists: %1" ).arg( query.lastError().text() ); return false; @@ -2489,7 +2555,7 @@ bool QgsMssqlProviderMetadata::styleExists( const QString &uri, const QString &s .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ) .arg( QgsMssqlProvider::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ); - if ( !query.exec( checkQuery ) ) + if ( !LoggedExecMetadata( query, checkQuery, uri ) ) { errorCause = QObject::tr( "Checking for style failed: %1" ).arg( query.lastError().text() ); return false; @@ -2527,7 +2593,10 @@ bool QgsMssqlProviderMetadata::saveStyle( const QString &uri, QSqlQuery query = QSqlQuery( db->db() ); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) ) ) + + QString sql { QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) }; + + if ( !LoggedExecMetadata( query, sql, uri ) ) { QgsDebugMsg( query.lastError().text() ); return false; @@ -2535,24 +2604,28 @@ bool QgsMssqlProviderMetadata::saveStyle( const QString &uri, if ( query.isActive() && query.next() && query.value( 0 ).toInt() == 0 ) { QgsDebugMsgLevel( QStringLiteral( "Need to create styles table" ), 2 ); - const bool execOk = query.exec( QString( "CREATE TABLE [dbo].[layer_styles](" - "[id] int IDENTITY(1,1) PRIMARY KEY," - "[f_table_catalog] [varchar](1024) NULL," - "[f_table_schema] [varchar](1024) NULL," - "[f_table_name] [varchar](1024) NULL," - "[f_geometry_column] [varchar](1024) NULL," - "[styleName] [varchar](1024) NULL," - "[styleQML] [text] NULL," - "[styleSLD] [text] NULL," - "[useAsDefault] [int] NULL," - "[description] [text] NULL," - "[owner] [varchar](1024) NULL," - "[ui] [text] NULL," - "[update_time] [datetime] NULL" - ") ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]" ) ); + + sql = QStringLiteral( "CREATE TABLE [dbo].[layer_styles](" + "[id] int IDENTITY(1,1) PRIMARY KEY," + "[f_table_catalog] [varchar](1024) NULL," + "[f_table_schema] [varchar](1024) NULL," + "[f_table_name] [varchar](1024) NULL," + "[f_geometry_column] [varchar](1024) NULL," + "[styleName] [varchar](1024) NULL," + "[styleQML] [text] NULL," + "[styleSLD] [text] NULL," + "[useAsDefault] [int] NULL," + "[description] [text] NULL," + "[owner] [varchar](1024) NULL," + "[ui] [text] NULL," + "[update_time] [datetime] NULL" + ") ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]" ); + + const bool execOk = LoggedExecMetadata( query, sql, uri ); if ( !execOk ) { - errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions. Please contact your database admin" ); + const QString error { QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions. Please contact your database admin" ) }; + errCause = error; return false; } query.finish(); @@ -2573,38 +2646,38 @@ bool QgsMssqlProviderMetadata::saveStyle( const QString &uri, // replaced by the QString.arg function. To ensure that the final SQL string is not corrupt these // two values are both replaced in the final .arg call of the string construction. - QString sql = QString( "INSERT INTO layer_styles" - "(f_table_catalog,f_table_schema,f_table_name,f_geometry_column,styleName,styleQML,styleSLD,useAsDefault,description,owner%11" - ") VALUES (" - "%1,%2,%3,%4,%5,%6,%7,%8,%9,%10%12" - ")" ) - .arg( QgsMssqlProvider::quotedValue( dsUri.database() ) ) - .arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) ) - .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) - .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ) - .arg( QgsMssqlProvider::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) - .arg( QgsMssqlProvider::quotedValue( qmlStyle ) ) - .arg( QgsMssqlProvider::quotedValue( sldStyle ) ) - .arg( useAsDefault ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) - .arg( QgsMssqlProvider::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) - .arg( QgsMssqlProvider::quotedValue( dsUri.username() ) ) - .arg( uiFileColumn ) - .arg( uiFileValue ); + sql = QStringLiteral( "INSERT INTO layer_styles" + "(f_table_catalog,f_table_schema,f_table_name,f_geometry_column,styleName,styleQML,styleSLD,useAsDefault,description,owner%11" + ") VALUES (" + "%1,%2,%3,%4,%5,%6,%7,%8,%9,%10%12" + ")" ) + .arg( QgsMssqlProvider::quotedValue( dsUri.database() ) ) + .arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) ) + .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) + .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ) + .arg( QgsMssqlProvider::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ) + .arg( QgsMssqlProvider::quotedValue( qmlStyle ) ) + .arg( QgsMssqlProvider::quotedValue( sldStyle ) ) + .arg( useAsDefault ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) + .arg( QgsMssqlProvider::quotedValue( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) ) + .arg( QgsMssqlProvider::quotedValue( dsUri.username() ) ) + .arg( uiFileColumn ) + .arg( uiFileValue ); - const QString checkQuery = QString( "SELECT styleName" - " FROM layer_styles" - " WHERE f_table_catalog=%1" - " AND f_table_schema=%2" - " AND f_table_name=%3" - " AND f_geometry_column=%4" - " AND styleName=%5" ) + const QString checkQuery = QStringLiteral( "SELECT styleName" + " FROM layer_styles" + " WHERE f_table_catalog=%1" + " AND f_table_schema=%2" + " AND f_table_name=%3" + " AND f_geometry_column=%4" + " AND styleName=%5" ) .arg( QgsMssqlProvider::quotedValue( dsUri.database() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ) .arg( QgsMssqlProvider::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ); - if ( !query.exec( checkQuery ) ) + if ( !LoggedExecMetadata( query, checkQuery, uri ) ) { QgsDebugMsg( query.lastError().text() ); QgsDebugMsg( QStringLiteral( "Check Query failed" ) ); @@ -2652,7 +2725,8 @@ bool QgsMssqlProviderMetadata::saveStyle( const QString &uri, QgsDebugMsgLevel( QStringLiteral( "Inserting styles" ), 2 ); QgsDebugMsgLevel( sql, 2 ); - const bool execOk = query.exec( sql ); + + const bool execOk = LoggedExecMetadata( query, sql, uri ); if ( !execOk ) { @@ -2677,8 +2751,12 @@ QString QgsMssqlProviderMetadata::loadStyle( const QString &uri, QString &errCau } QSqlQuery query = QSqlQuery( db->db() ); + query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) ) ) + + const QString sql { QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) }; + + if ( !LoggedExecMetadata( query, sql, uri ) ) { errCause = tr( "Could not check if layer_styles table exists: %1" ).arg( query.lastError().text() ); return QString(); @@ -2706,7 +2784,7 @@ QString QgsMssqlProviderMetadata::loadStyle( const QString &uri, QString &errCau .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ); - if ( !query.exec( selectQmlQuery ) ) + if ( !LoggedExecMetadata( query, selectQmlQuery, uri ) ) { QgsDebugMsgLevel( QStringLiteral( "Load of style failed" ), 2 ); const QString msg = query.lastError().text(); @@ -2742,8 +2820,10 @@ int QgsMssqlProviderMetadata::listStyles( const QString &uri, QSqlQuery query = QSqlQuery( db->db() ); query.setForwardOnly( true ); + QString sql { QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) }; + // check if layer_styles table already exist - if ( !query.exec( QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) ) ) + if ( !LoggedExecMetadata( query, sql, uri ) ) { const QString msg = query.lastError().text(); errCause = msg; @@ -2767,7 +2847,9 @@ int QgsMssqlProviderMetadata::listStyles( const QString &uri, .arg( QgsMssqlProvider::quotedValue( dsUri.schema() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ); - bool queryOk = query.exec( selectRelatedQuery ); + + + bool queryOk = LoggedExecMetadata( query, selectRelatedQuery, uri ); if ( !queryOk ) { QgsDebugMsg( query.lastError().text() ); @@ -2791,7 +2873,8 @@ int QgsMssqlProviderMetadata::listStyles( const QString &uri, .arg( QgsMssqlProvider::quotedValue( dsUri.table() ) ) .arg( QgsMssqlProvider::quotedValue( dsUri.geometryColumn() ) ); QgsDebugMsgLevel( selectOthersQuery, 2 ); - queryOk = query.exec( selectOthersQuery ); + + queryOk = LoggedExecMetadata( query, selectOthersQuery, uri ); if ( !queryOk ) { QgsDebugMsg( query.lastError().text() ); @@ -2828,7 +2911,9 @@ QString QgsMssqlProviderMetadata::getStyleById( const QString &uri, const QStrin QSqlQuery query = QSqlQuery( db->db() ); query.setForwardOnly( true ); - if ( !query.exec( QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) ) ) + const QString sql { QStringLiteral( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME= N'layer_styles'" ) }; + + if ( !LoggedExecMetadata( query, sql, uri ) ) { errCause = tr( "Could not check if layer_styles table exists: %1" ).arg( query.lastError().text() ); return QString(); @@ -2846,7 +2931,8 @@ QString QgsMssqlProviderMetadata::getStyleById( const QString &uri, const QStrin QString style; const QString selectQmlQuery = QStringLiteral( "SELECT styleQml FROM layer_styles WHERE id=%1" ).arg( QgsMssqlProvider::quotedValue( styleId ) ); - const bool queryOk = query.exec( selectQmlQuery ); + + const bool queryOk = LoggedExecMetadata( query, selectQmlQuery, uri ); if ( !queryOk ) { QgsDebugMsg( query.lastError().text() ); @@ -2995,6 +3081,29 @@ QString QgsMssqlProviderMetadata::encodeUri( const QVariantMap &parts ) const return dsUri.uri(); } +bool QgsMssqlProviderMetadata::execLogged( QSqlQuery &qry, const QString &sql, const QString &uri, const QString &queryOrigin ) const +{ + QgsDatabaseQueryLogWrapper logWrapper{ sql, uri, QStringLiteral( "mssql" ), QStringLiteral( "QgsMssqlProviderMetadata" ), queryOrigin }; + const bool res { qry.exec( sql ) }; + if ( ! res ) + { + logWrapper.setError( qry.lastError().text() ); + } + else + { + if ( qry.isSelect() ) + { + logWrapper.setFetchedRows( qry.size() ); + } + else + { + logWrapper.setFetchedRows( qry.numRowsAffected() ); + } + } + logWrapper.setQuery( qry.lastQuery() ); + return res; +} + QGISEXTERN QgsProviderMetadata *providerMetadataFactory() { return new QgsMssqlProviderMetadata(); @@ -3156,7 +3265,8 @@ bool QgsMssqlProvider::getExtentFromGeometryColumns( QgsRectangle &extent ) cons "AND NOT (qgis_xmin IS NULL OR qgis_xmax IS NULL OR qgis_ymin IS NULL OR qgis_ymax IS NULL)" ); const QString statement = sql.arg( quotedValue( mTableName ), quotedValue( mSchemaName ) ); - if ( query.exec( statement ) && query.isActive() ) + + if ( LoggedExec( query, statement ) && query.isActive() ) { query.next(); if ( query.isValid() ) @@ -3182,7 +3292,8 @@ bool QgsMssqlProvider::getPrimaryKeyFromGeometryColumns( QStringList &primaryKey const QString sql = QStringLiteral( "SELECT qgis_pkey FROM geometry_columns WHERE f_table_name = '%1' AND NOT qgis_pkey IS NULL" ); const QString statement = sql.arg( mTableName ); - if ( query.exec( statement ) && query.isActive() ) + + if ( LoggedExec( query, statement ) && query.isActive() ) { query.next(); if ( query.isValid() ) diff --git a/src/providers/mssql/qgsmssqlprovider.h b/src/providers/mssql/qgsmssqlprovider.h index 867b8a719ec..552430d2943 100644 --- a/src/providers/mssql/qgsmssqlprovider.h +++ b/src/providers/mssql/qgsmssqlprovider.h @@ -176,6 +176,8 @@ class QgsMssqlProvider final: public QgsVectorDataProvider private: + bool execLogged( QSqlQuery &qry, const QString &sql, const QString &queryOrigin = QString() ) const; + //! Fields QgsFields mAttributeFields; QMap mDefaultValues; @@ -328,6 +330,10 @@ class QgsMssqlProviderMetadata final: public QgsProviderMetadata QVariantMap decodeUri( const QString &uri ) const override; QString encodeUri( const QVariantMap &parts ) const override; + private: + + bool execLogged( QSqlQuery &qry, const QString &sql, const QString &uri, const QString &queryOrigin = QString() ) const; + }; #endif // QGSMSSQLPROVIDER_H diff --git a/src/providers/oracle/qgsoracleconn.cpp b/src/providers/oracle/qgsoracleconn.cpp index 3448d7d2f5d..4556b4e5cc6 100644 --- a/src/providers/oracle/qgsoracleconn.cpp +++ b/src/providers/oracle/qgsoracleconn.cpp @@ -27,6 +27,8 @@ #include "qgsvariantutils.h" #include +#include +#include QMap QgsOracleConn::sConnections; int QgsOracleConn::snConnections = 0; @@ -71,7 +73,8 @@ QgsOracleConn::QgsOracleConn( QgsDataSourceUri uri, bool transaction ) { QgsDebugMsgLevel( QStringLiteral( "New Oracle connection for " ) + uri.connectionInfo( false ), 2 ); - uri = QgsDataSourceUri( uri.connectionInfo( true ) ); + mConnInfo = uri.connectionInfo( true ); + uri = QgsDataSourceUri( mConnInfo ); QString database = databaseName( uri.database(), uri.host(), uri.port() ); QgsDebugMsgLevel( QStringLiteral( "New Oracle database " ) + database, 2 ); @@ -226,6 +229,29 @@ void QgsOracleConn::unref() delete this; } +QString QgsOracleConn::getLastExecutedQuery( const QSqlQuery &query ) +{ + QString str = query.lastQuery(); + QMapIterator it( query.boundValues() ); + while ( it.hasNext() ) + { + it.next(); + const QVariant &var { it.value().toString() }; + QSqlField field( QString( ), var.type() ); + if ( var.isNull() ) + { + field.clear(); + } + else + { + field.setValue( var ); + } + const QString formatV = query.driver()->formatValue( field ); + str.replace( it.key(), formatV ); + } + return str; +} + bool QgsOracleConn::exec( QSqlQuery &qry, const QString &sql, const QVariantList ¶ms ) { QgsDebugMsgLevel( QStringLiteral( "SQL: %1" ).arg( sql ), 4 ); @@ -249,6 +275,49 @@ bool QgsOracleConn::exec( QSqlQuery &qry, const QString &sql, const QVariantList qry.lastError().text() ) ); } + return res; + +} + +bool QgsOracleConn::execLogged( QSqlQuery &qry, const QString &sql, const QVariantList ¶ms, const QString &originatorClass, const QString &queryOrigin ) +{ + QgsDebugMsgLevel( QStringLiteral( "SQL: %1" ).arg( sql ), 4 ); + + QgsDatabaseQueryLogWrapper logWrapper { sql, mConnInfo, QStringLiteral( "oracle" ), originatorClass, queryOrigin }; + + bool res = qry.prepare( sql ); + if ( res ) + { + for ( const auto ¶m : params ) + { + QgsDebugMsgLevel( QStringLiteral( " ARG: %1 [%2]" ).arg( param.toString(), param.typeName() ), 4 ); + qry.addBindValue( param ); + } + + res = qry.exec(); + } + + logWrapper.setQuery( getLastExecutedQuery( qry ) ); + + if ( !res ) + { + logWrapper.setError( qry.lastError().text() ); + QgsDebugMsg( QStringLiteral( "SQL: %1\nERROR: %2" ) + .arg( qry.lastQuery(), + qry.lastError().text() ) ); + } + else + { + if ( qry.isSelect() ) + { + logWrapper.setFetchedRows( qry.size() ); + } + else + { + logWrapper.setFetchedRows( qry.numRowsAffected() ); + } + } + return res; } @@ -257,10 +326,12 @@ QStringList QgsOracleConn::pkCandidates( const QString &ownerName, const QString QStringList cols; QSqlQuery qry( mDatabase ); - if ( !exec( qry, QStringLiteral( "SELECT column_name FROM all_tab_columns WHERE owner=? AND table_name=? ORDER BY column_id" ), - QVariantList() << ownerName << viewName ) ) + + if ( !LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, QStringLiteral( "SELECT column_name FROM all_tab_columns WHERE owner=? AND table_name=? ORDER BY column_id" ), + QVariantList() << ownerName << viewName ) ) { - QgsMessageLog::logMessage( tr( "SQL: %1 [owner: %2 table_name: %3]\nerror: %4\n" ).arg( qry.lastQuery(), qry.lastError().text(), ownerName, viewName ), tr( "Oracle" ) ); + const QString error { tr( "SQL: %1 [owner: %2 table_name: %3]\nerror: %4\n" ).arg( qry.lastQuery(), qry.lastError().text(), ownerName, viewName ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return cols; } @@ -308,9 +379,11 @@ bool QgsOracleConn::tableInfo( const QString &schema, bool geometryColumnsOnly, } QSqlQuery qry( mDatabase ); - if ( !exec( qry, sql, QVariantList() ) ) + + if ( !LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql, QVariantList() ) ) { - QgsMessageLog::logMessage( tr( "Querying available tables failed.\nSQL: %1\nerror: %2\n" ).arg( qry.lastQuery(), qry.lastError().text() ), tr( "Oracle" ) ); + const QString error { tr( "Querying available tables failed.\nSQL: %1\nerror: %2\n" ).arg( qry.lastQuery(), qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return false; } @@ -326,8 +399,10 @@ bool QgsOracleConn::tableInfo( const QString &schema, bool geometryColumnsOnly, layerProperty.pkCols.clear(); mLayersSupported << layerProperty; + } + if ( mLayersSupported.size() == 0 ) { QgsMessageLog::logMessage( tr( "Database connection was successful, but the accessible tables could not be determined." ), tr( "Oracle" ) ); @@ -417,19 +492,22 @@ bool QgsOracleConn::exec( const QString &query, bool logError, QString *errorMes QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 ); QSqlQuery qry( mDatabase ); + if ( !exec( qry, query, QVariantList() ) ) { QString error = qry.lastError().text(); if ( logError ) { - QgsMessageLog::logMessage( tr( "Connection error: %1 returned %2" ) - .arg( query, error ), + const QString errorMsg { tr( "Connection error: %1 returned %2" ) + .arg( query, error ) }; + QgsMessageLog::logMessage( errorMsg, tr( "Oracle" ) ); } else { - QgsDebugMsg( QStringLiteral( "Connection error: %1 returned %2" ) - .arg( query, error ) ); + const QString errorMsg { QStringLiteral( "Connection error: %1 returned %2" ) + .arg( query, error ) }; + QgsDebugMsg( errorMsg ); } if ( errorMessage ) *errorMessage = error; @@ -438,12 +516,62 @@ bool QgsOracleConn::exec( const QString &query, bool logError, QString *errorMes return true; } +bool QgsOracleConn::execLogged( const QString &query, bool logError, QString *errorMessage, const QString &originatorClass, const QString &queryOrigin ) +{ + + QMutexLocker locker( &mLock ); + QgsDatabaseQueryLogWrapper logWrapper { query, mConnInfo, QStringLiteral( "oracle" ), originatorClass, queryOrigin }; + + QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 ); + + QSqlQuery qry( mDatabase ); + + const bool res { !exec( qry, query, QVariantList() ) }; + + logWrapper.setQuery( qry.lastQuery() ); + + if ( ! res ) + { + const QString error = qry.lastError().text(); + logWrapper.setError( error ); + if ( logError ) + { + const QString errorMsg { tr( "Connection error: %1 returned %2" ) + .arg( query, error ) }; + QgsMessageLog::logMessage( errorMsg, + tr( "Oracle" ) ); + } + else + { + const QString errorMsg { QStringLiteral( "Connection error: %1 returned %2" ) + .arg( query, error ) }; + QgsDebugMsg( errorMsg ); + } + if ( errorMessage ) + *errorMessage = error; + return false; + } + else + { + if ( qry.isSelect() ) + { + logWrapper.setFetchedRows( qry.size() ); + } + else + { + logWrapper.setFetchedRows( qry.numRowsAffected() ); + } + } + + return true; +} + bool QgsOracleConn::begin( QSqlDatabase &db ) { QMutexLocker locker( &mLock ); if ( mTransaction ) { - return exec( QStringLiteral( "SAVEPOINT sp%1" ).arg( ++mSavePointId ) ); + return LoggedExec( QStringLiteral( "QgsOracleConn" ), QStringLiteral( "SAVEPOINT sp%1" ).arg( ++mSavePointId ) ); } else { @@ -456,7 +584,7 @@ bool QgsOracleConn::commit( QSqlDatabase &db ) QMutexLocker locker( &mLock ); if ( mTransaction ) { - return exec( QStringLiteral( "SAVEPOINT sp%1" ).arg( ++mSavePointId ) ); + return LoggedExec( QStringLiteral( "QgsOracleConn" ), QStringLiteral( "SAVEPOINT sp%1" ).arg( ++mSavePointId ) ); } else { @@ -469,7 +597,7 @@ bool QgsOracleConn::rollback( QSqlDatabase &db ) QMutexLocker locker( &mLock ); if ( mTransaction ) { - return exec( QStringLiteral( "ROLLBACK TO SAVEPOINT sp%1" ).arg( mSavePointId ) ); + return LoggedExec( QStringLiteral( "QgsOracleConn" ), QStringLiteral( "ROLLBACK TO SAVEPOINT sp%1" ).arg( mSavePointId ) ); } else { @@ -581,14 +709,15 @@ void QgsOracleConn::retrieveLayerTypes( QgsOracleLayerProperty &layerProperty, b sql += QLatin1String( " FROM %2 t WHERE NOT t.%1 IS NULL%3" ); - if ( !exec( qry, sql - .arg( quotedIdentifier( layerProperty.geometryColName ), - table, - where.isEmpty() ? QString() : QStringLiteral( " AND (%1)" ).arg( where ) ), QVariantList() ) ) + if ( !LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql + .arg( quotedIdentifier( layerProperty.geometryColName ), + table, + where.isEmpty() ? QString() : QStringLiteral( " AND (%1)" ).arg( where ) ), QVariantList() ) ) { - QgsMessageLog::logMessage( tr( "SQL: %1\nerror: %2\n" ) - .arg( qry.lastQuery(), - qry.lastError().text() ), + const QString error { tr( "SQL: %1\nerror: %2\n" ) + .arg( qry.lastQuery(), + qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return; } @@ -597,8 +726,10 @@ void QgsOracleConn::retrieveLayerTypes( QgsOracleLayerProperty &layerProperty, b layerProperty.srids.clear(); QSet srids; + long long fetchedRows { 0 }; while ( qry.next() ) { + fetchedRows++; if ( detectedType == QgsWkbTypes::Unknown ) { QgsWkbTypes::Type type = wkbTypeFromDatabase( qry.value( 0 ).toInt() ); @@ -926,7 +1057,8 @@ bool QgsOracleConn::hasSpatial() if ( mHasSpatial == -1 ) { QSqlQuery qry( mDatabase ); - mHasSpatial = exec( qry, QStringLiteral( "SELECT 1 FROM v$option WHERE parameter='Spatial' AND value='TRUE'" ), QVariantList() ) && qry.next(); + const QString sql { QStringLiteral( "SELECT 1 FROM v$option WHERE parameter='Spatial' AND value='TRUE'" ) }; + mHasSpatial = LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql, QVariantList() ) && qry.next(); } return mHasSpatial; @@ -935,16 +1067,17 @@ bool QgsOracleConn::hasSpatial() int QgsOracleConn::version() { QSqlQuery qry( mDatabase ); - QString sql = QStringLiteral( "SELECT VERSION FROM PRODUCT_COMPONENT_VERSION" ); - if ( exec( qry, sql, QVariantList() ) && qry.next() ) + const QString sql = QStringLiteral( "SELECT VERSION FROM PRODUCT_COMPONENT_VERSION" ); + if ( LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql, QVariantList() ) && qry.next() ) { return qry.value( 0 ).toString().split( '.' ).at( 0 ).toInt(); } else { - QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), tr( "Oracle" ) ); + const QString error { tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return -1; } } @@ -956,7 +1089,8 @@ QString QgsOracleConn::currentUser() if ( mCurrentUser.isNull() ) { QSqlQuery qry( mDatabase ); - if ( exec( qry, QStringLiteral( "SELECT user FROM dual" ), QVariantList() ) && qry.next() ) + const QString sql { QStringLiteral( "SELECT user FROM dual" ) }; + if ( LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql, QVariantList() ) && qry.next() ) { mCurrentUser = qry.value( 0 ).toString(); } @@ -993,11 +1127,12 @@ QString QgsOracleConn::getSpatialIndexName( const QString &ownerName, const QStr QString name; QSqlQuery qry( mDatabase ); - if ( exec( qry, QString( "SELECT i.index_name,i.domidx_opstatus" - " FROM all_indexes i" - " JOIN all_ind_columns c ON i.owner=c.index_owner AND i.index_name=c.index_name AND c.column_name=?" - " WHERE i.table_owner=? AND i.table_name=? AND i.ityp_owner='MDSYS' AND i.ityp_name='SPATIAL_INDEX'" ), - QVariantList() << geometryColumn << ownerName << tableName ) ) + + if ( LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, QStringLiteral( "SELECT i.index_name,i.domidx_opstatus" + " FROM all_indexes i" + " JOIN all_ind_columns c ON i.owner=c.index_owner AND i.index_name=c.index_name AND c.column_name=?" + " WHERE i.table_owner=? AND i.table_name=? AND i.ityp_owner='MDSYS' AND i.ityp_name='SPATIAL_INDEX'" ), + QVariantList() << geometryColumn << ownerName << tableName ) ) { if ( qry.next() ) { @@ -1025,11 +1160,12 @@ QString QgsOracleConn::getSpatialIndexName( const QString &ownerName, const QStr } else { - QgsMessageLog::logMessage( tr( "Probing for spatial index on column %1.%2.%3 failed [%4]" ) - .arg( ownerName ) - .arg( tableName ) - .arg( geometryColumn ) - .arg( qry.lastError().text() ), + const QString error { tr( "Probing for spatial index on column %1.%2.%3 failed [%4]" ) + .arg( ownerName ) + .arg( tableName ) + .arg( geometryColumn ) + .arg( qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); isValid = false; @@ -1043,21 +1179,25 @@ QString QgsOracleConn::createSpatialIndex( const QString &ownerName, const QStri QSqlQuery qry( mDatabase ); int n = 0; - if ( exec( qry, QString( "SELECT coalesce(substr(max(index_name),10),'0') FROM all_indexes WHERE index_name LIKE 'QGIS_IDX_%' ESCAPE '#' ORDER BY index_name" ), QVariantList() ) && + const QString sql { QStringLiteral( "SELECT coalesce(substr(max(index_name),10),'0') FROM all_indexes WHERE index_name LIKE 'QGIS_IDX_%' ESCAPE '#' ORDER BY index_name" ) }; + + if ( LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql, QVariantList() ) && qry.next() ) { n = qry.value( 0 ).toInt() + 1; } - if ( !exec( qry, QString( "CREATE INDEX QGIS_IDX_%1 ON %2.%3(%4) INDEXTYPE IS MDSYS.SPATIAL_INDEX PARALLEL" ) - .arg( n, 10, 10, QChar( '0' ) ) - .arg( quotedIdentifier( ownerName ) ) - .arg( quotedIdentifier( tableName ) ) - .arg( quotedIdentifier( geometryColumn ) ), QVariantList() ) ) + const QString sql2 { QStringLiteral( "CREATE INDEX QGIS_IDX_%1 ON %2.%3(%4) INDEXTYPE IS MDSYS.SPATIAL_INDEX PARALLEL" ) + .arg( n, 10, 10, QChar( '0' ) ) + .arg( quotedIdentifier( ownerName ) ) + .arg( quotedIdentifier( tableName ) ) + .arg( quotedIdentifier( geometryColumn ) ) }; + if ( !LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, sql2, QVariantList() ) ) { - QgsMessageLog::logMessage( tr( "Creation spatial index failed.\nSQL: %1\nError: %2" ) - .arg( qry.lastQuery() ) - .arg( qry.lastError().text() ), + const QString error { tr( "Creation spatial index failed.\nSQL: %1\nError: %2" ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return QString(); } @@ -1071,15 +1211,16 @@ QStringList QgsOracleConn::getPrimaryKeys( const QString &ownerName, const QStri QStringList result; - if ( !exec( qry, QString( "SELECT column_name" - " FROM all_cons_columns a" - " JOIN all_constraints b ON a.constraint_name=b.constraint_name AND a.owner=b.owner" - " WHERE b.constraint_type='P' AND b.owner=? AND b.table_name=?" ), - QVariantList() << ownerName << tableName ) ) + if ( !LoggedExecPrivate( QStringLiteral( "QgsOracleConn" ), qry, QStringLiteral( "SELECT column_name" + " FROM all_cons_columns a" + " JOIN all_constraints b ON a.constraint_name=b.constraint_name AND a.owner=b.owner" + " WHERE b.constraint_type='P' AND b.owner=? AND b.table_name=?" ), + QVariantList() << ownerName << tableName ) ) { - QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), tr( "Oracle" ) ); + const QString error { tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return result; } diff --git a/src/providers/oracle/qgsoracleconn.h b/src/providers/oracle/qgsoracleconn.h index 1db743d557d..d5b00ced3f9 100644 --- a/src/providers/oracle/qgsoracleconn.h +++ b/src/providers/oracle/qgsoracleconn.h @@ -31,6 +31,7 @@ #include "qgslogger.h" #include "qgsdatasourceuri.h" #include "qgsvectordataprovider.h" +#include "qgsdbquerylog.h" #include #include @@ -112,6 +113,13 @@ struct QgsOracleLayerProperty #endif }; + +#include "qgsconfig.h" +constexpr int sOracleConQueryLogFilePrefixLength = CMAKE_SOURCE_DIR[sizeof( CMAKE_SOURCE_DIR ) - 1] == '/' ? sizeof( CMAKE_SOURCE_DIR ) + 1 : sizeof( CMAKE_SOURCE_DIR ); +#define LoggedExec(_class, query) execLogged( query, true, nullptr, _class, QString(QString( __FILE__ ).mid( sOracleConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) +#define LoggedExecPrivate(_class, query, sql, params ) execLogged( query, sql, params, _class, QString(QString( __FILE__ ).mid( sOracleConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) + + /** * Wraps acquireConnection() and releaseConnection() from a QgsOracleConnPool. * This can be used to ensure a connection is correctly released when scope ends @@ -157,6 +165,7 @@ class QgsOracleConn : public QObject static QString quotedValue( const QVariant &value, QVariant::Type type = QVariant::Invalid ); bool exec( const QString &query, bool logError = true, QString *errorMessage = nullptr ); + bool execLogged( const QString &sql, bool logError = true, QString *errorMessage = nullptr, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); bool begin( QSqlDatabase &db ); bool commit( QSqlDatabase &db ); @@ -253,11 +262,14 @@ class QgsOracleConn : public QObject operator QSqlDatabase() { return mDatabase; } + static QString getLastExecutedQuery( const QSqlQuery &query ); + private: explicit QgsOracleConn( QgsDataSourceUri uri, bool transaction ); ~QgsOracleConn() override; bool exec( QSqlQuery &qry, const QString &sql, const QVariantList ¶ms ); + bool execLogged( QSqlQuery &qry, const QString &sql, const QVariantList ¶ms, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); //! reference count int mRef; @@ -280,6 +292,9 @@ class QgsOracleConn : public QObject static QMap sConnections; static int snConnections; static QMap sBrokenConnections; + + // Connection URI string representation for query logger + QString mConnInfo; }; #endif diff --git a/src/providers/oracle/qgsoracledataitems.cpp b/src/providers/oracle/qgsoracledataitems.cpp index ef8d944d873..2048e697e5b 100644 --- a/src/providers/oracle/qgsoracledataitems.cpp +++ b/src/providers/oracle/qgsoracledataitems.cpp @@ -60,10 +60,10 @@ bool deleteLayer( const QString &uri, QString &errCause ) QSqlQuery qry( *conn ); // check the geometry column count - if ( !QgsOracleProvider::exec( qry, QString( "SELECT count(*)" - " FROM user_tab_columns" - " WHERE table_name=? AND data_type='SDO_GEOMETRY' AND data_type_owner='MDSYS'" ), - QVariantList() << tableName ) + if ( !QgsOracleProvider::execLoggedStatic( qry, QString( "SELECT count(*)" + " FROM user_tab_columns" + " WHERE table_name=? AND data_type='SDO_GEOMETRY' AND data_type_owner='MDSYS'" ), + QVariantList() << tableName, dsUri.uri(), QStringLiteral( "QgsOracleLayerItem" ), QGS_QUERY_LOG_ORIGIN ) || !qry.next() ) { errCause = QObject::tr( "Unable to determine number of geometry columns of layer %1.%2: \n%3" ) @@ -97,7 +97,7 @@ bool deleteLayer( const QString &uri, QString &errCause ) args << tableName; } - if ( !QgsOracleProvider::exec( qry, dropTable, QVariantList() ) ) + if ( !QgsOracleProvider::execLoggedStatic( qry, dropTable, QVariantList(), dsUri.uri(), QStringLiteral( "QgsOracleLayerItem" ), QGS_QUERY_LOG_ORIGIN ) ) { errCause = QObject::tr( "Unable to delete layer %1.%2: \n%3" ) .arg( ownerName ) @@ -107,7 +107,7 @@ bool deleteLayer( const QString &uri, QString &errCause ) return false; } - if ( !QgsOracleProvider::exec( qry, cleanView, args ) ) + if ( !QgsOracleProvider::execLoggedStatic( qry, cleanView, args, dsUri.uri(), QStringLiteral( "QgsOracleLayerItem" ), QGS_QUERY_LOG_ORIGIN ) ) { errCause = QObject::tr( "Unable to clean metadata %1.%2: \n%3" ) .arg( ownerName ) @@ -544,7 +544,7 @@ QVector QgsOracleRootItem::createChildren() { QVector connections; const QStringList list = QgsOracleConn::connectionList(); - for ( QString connName : list ) + for ( const QString &connName : std::as_const( list ) ) { connections << new QgsOracleConnectionItem( this, connName, mPath + '/' + connName ); } diff --git a/src/providers/oracle/qgsoraclefeatureiterator.cpp b/src/providers/oracle/qgsoraclefeatureiterator.cpp index 69a169c8c3e..d2a184e46d0 100644 --- a/src/providers/oracle/qgsoraclefeatureiterator.cpp +++ b/src/providers/oracle/qgsoraclefeatureiterator.cpp @@ -296,9 +296,10 @@ bool QgsOracleFeatureIterator::fetchFeature( QgsFeature &feature ) mRewind = false; if ( !execQuery( mSql, mArgs, 1 ) ) { - QgsMessageLog::logMessage( QObject::tr( "Fetching features failed.\nSQL: %1\nError: %2" ) - .arg( mQry.lastQuery(), - mQry.lastError().text() ), + const QString error { QObject::tr( "Fetching features failed.\nSQL: %1\nError: %2" ) + .arg( mQry.lastQuery(), + mQry.lastError().text() ) }; + QgsMessageLog::logMessage( error, QObject::tr( "Oracle" ) ); return false; } @@ -531,13 +532,16 @@ bool QgsOracleFeatureIterator::openQuery( const QString &whereClause, const QVar QgsDebugMsgLevel( QStringLiteral( "Fetch features: %1" ).arg( query ), 2 ); mSql = query; mArgs = args; + if ( !execQuery( query, args, 1 ) ) { + + const QString error { QObject::tr( "Fetching features failed.\nSQL: %1\nError: %2" ) + .arg( mQry.lastQuery(), + mQry.lastError().text() ) }; if ( showLog ) { - QgsMessageLog::logMessage( QObject::tr( "Fetching features failed.\nSQL: %1\nError: %2" ) - .arg( mQry.lastQuery(), - mQry.lastError().text() ), + QgsMessageLog::logMessage( error, QObject::tr( "Oracle" ) ); } return false; @@ -554,7 +558,7 @@ bool QgsOracleFeatureIterator::openQuery( const QString &whereClause, const QVar bool QgsOracleFeatureIterator::execQuery( const QString &query, const QVariantList &args, int retryCount ) { lock(); - if ( !QgsOracleProvider::exec( mQry, query, args ) ) + if ( !QgsOracleProvider::execLoggedStatic( mQry, query, args, mSource->mUri.uri(), QStringLiteral( "QgsOracleFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ) ) { unlock(); if ( retryCount != 0 ) diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp index 9f5407ed652..2695ff40e23 100644 --- a/src/providers/oracle/qgsoracleprovider.cpp +++ b/src/providers/oracle/qgsoracleprovider.cpp @@ -29,6 +29,7 @@ #include "qgscoordinatereferencesystem.h" #include "qgsvectorlayerexporter.h" #include "qgslogger.h" +#include "qgsdbquerylog.h" #include "qgsoracleprovider.h" #include "qgsoracletablemodel.h" @@ -49,6 +50,10 @@ #include "ocispatial/wkbptr.h" + +#define LoggedExecStatic(query, sql, args, uri ) execLoggedStatic( query, sql, args, uri, QStringLiteral( "QgsOracleProvider" ), QString(QString( __FILE__ ).mid( sOracleConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) + + const QString ORACLE_KEY = "oracle"; const QString ORACLE_DESCRIPTION = "Oracle data provider"; @@ -279,13 +284,14 @@ QgsOracleConn *QgsOracleProvider::connectionRO() const return mTransaction ? mTransaction->connection() : QgsOracleConn::connectDb( mUri, false ); } -bool QgsOracleProvider::exec( QSqlQuery &qry, QString sql, const QVariantList &args ) +bool QgsOracleProvider::execLoggedStatic( QSqlQuery &qry, const QString &sql, const QVariantList &args, const QString &uri, const QString &originatorClass, const QString &queryOrigin ) { - QgsDebugMsgLevel( QStringLiteral( "SQL: %1" ).arg( sql ), 4 ); + QgsDatabaseQueryLogWrapper logWrapper { sql, uri, QStringLiteral( "oracle" ), originatorClass, queryOrigin }; qry.setForwardOnly( true ); bool res = qry.prepare( sql ); + if ( res ) { for ( const auto &arg : args ) @@ -303,6 +309,18 @@ bool QgsOracleProvider::exec( QSqlQuery &qry, QString sql, const QVariantList &a .arg( qry.lastError().text() ) ); } + logWrapper.setQuery( QgsOracleConn::getLastExecutedQuery( qry ) ); + logWrapper.setError( qry.lastError().text() ); + // ORACLE does not support size so this will always be -1 + // we leave it here in case this changes in the future + if ( qry.isSelect() ) + { + logWrapper.setFetchedRows( qry.size() ); + } + else + { + logWrapper.setFetchedRows( qry.numRowsAffected() ); + } return res; } @@ -565,13 +583,13 @@ bool QgsOracleProvider::loadFields() { QgsDebugMsgLevel( QStringLiteral( "Loading fields for table %1" ).arg( mTableName ), 2 ); - if ( exec( qry, QString( "SELECT comments FROM all_tab_comments WHERE owner=? AND table_name=?" ), - QVariantList() << mOwnerName << mTableName ) ) + if ( LoggedExecStatic( qry, QStringLiteral( "SELECT comments FROM all_tab_comments WHERE owner=? AND table_name=?" ), + QVariantList() << mOwnerName << mTableName, mUri.uri() ) ) { if ( qry.next() ) mDataComment = qry.value( 0 ).toString(); - else if ( exec( qry, QString( "SELECT comments FROM all_mview_comments WHERE owner=? AND mview_name=?" ), - QVariantList() << mOwnerName << mTableName ) ) + else if ( LoggedExecStatic( qry, QStringLiteral( "SELECT comments FROM all_mview_comments WHERE owner=? AND mview_name=?" ), + QVariantList() << mOwnerName << mTableName, mUri.uri() ) ) { if ( qry.next() ) mDataComment = qry.value( 0 ).toString(); @@ -579,28 +597,31 @@ bool QgsOracleProvider::loadFields() } else { - QgsMessageLog::logMessage( tr( "Loading comment for table %1.%2 failed [%3]" ) - .arg( mOwnerName ) - .arg( mTableName ) - .arg( qry.lastError().text() ), + const QString error { tr( "Loading comment for table %1.%2 failed [%3]" ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); } qry.finish(); - if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=? AND t.table_name=?" ), - QVariantList() << mOwnerName << mTableName ) ) + if ( LoggedExecStatic( qry, QStringLiteral( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=? AND t.table_name=?" ), + QVariantList() << mOwnerName << mTableName, mUri.uri() ) ) { while ( qry.next() ) { if ( qry.value( 0 ).toString() == mGeometryColumn ) continue; comments.insert( qry.value( 0 ).toString(), qry.value( 1 ).toString() ); + } } else { - QgsMessageLog::logMessage( tr( "Loading comment for columns of table %1.%2 failed [%3]" ).arg( mOwnerName ).arg( mTableName ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + const QString error { tr( "Loading comment for columns of table %1.%2 failed [%3]" ).arg( mOwnerName ).arg( mTableName ).arg( qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); } qry.finish(); @@ -638,10 +659,12 @@ bool QgsOracleProvider::loadFields() sql += " ORDER BY t.column_id"; - if ( exec( qry, sql, args ) ) + if ( LoggedExecStatic( qry, sql, args, mUri.uri() ) ) { + long long fetchedRows { 0 }; while ( qry.next() ) { + fetchedRows++; QString name = qry.value( 0 ).toString(); QString type = qry.value( 1 ).toString(); int prec = qry.value( 2 ).toInt(); @@ -678,13 +701,16 @@ bool QgsOracleProvider::loadFields() defvalues.insert( name, defValue ); alwaysGenerated.insert( name, alwaysGen ); } + } else { - QgsMessageLog::logMessage( tr( "Loading field types for table %1.%2 failed [%3]" ) - .arg( mOwnerName ) - .arg( mTableName ) - .arg( qry.lastError().text() ), + const QString error { tr( "Loading field types for table %1.%2 failed [%3]" ) + .arg( mOwnerName ) + .arg( mTableName ) + .arg( qry.lastError().text() ) }; + + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); } @@ -698,10 +724,12 @@ bool QgsOracleProvider::loadFields() { if ( !mHasSpatialIndex ) { - mHasSpatialIndex = qry.exec( QString( "SELECT %2 FROM %1 WHERE sdo_filter(%2,mdsys.sdo_geometry(2003,%3,NULL,mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(-1,-1,1,1)))='TRUE'" ) - .arg( mQuery ) - .arg( quotedIdentifier( mGeometryColumn ) ) - .arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) ); + const QString sql{ QStringLiteral( "SELECT %2 FROM %1 WHERE sdo_filter(%2,mdsys.sdo_geometry(2003,%3,NULL,mdsys.sdo_elem_info_array(1,1003,3),mdsys.sdo_ordinate_array(-1,-1,1,1)))='TRUE'" ) + .arg( mQuery ) + .arg( quotedIdentifier( mGeometryColumn ) ) + .arg( mSrid < 1 ? "NULL" : QString::number( mSrid ) ) }; + + mHasSpatialIndex = LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ); } if ( !mHasSpatialIndex ) @@ -716,9 +744,12 @@ bool QgsOracleProvider::loadFields() qry.finish(); - if ( !exec( qry, QString( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ), QVariantList() ) ) + const QString sql { QStringLiteral( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { - QgsMessageLog::logMessage( tr( "Retrieving fields from '%1' failed [%2]" ).arg( mQuery ).arg( qry.lastError().text() ), tr( "Oracle" ) ); + const QString error { tr( "Retrieving fields from '%1' failed [%2]" ).arg( mQuery ).arg( qry.lastError().text() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return false; } @@ -763,6 +794,7 @@ bool QgsOracleProvider::hasSufficientPermsAndCapabilities() mEnabledCapabilities |= QgsVectorDataProvider::CircularGeometries; QgsOracleConn *conn = connectionRO(); + QSqlQuery qry( *conn ); if ( !mIsQuery ) { @@ -778,58 +810,66 @@ bool QgsOracleProvider::hasSufficientPermsAndCapabilities() | QgsVectorDataProvider::RenameAttributes ; } - else if ( exec( qry, QString( "SELECT privilege FROM all_tab_privs WHERE table_schema=? AND table_name=? AND privilege IN ('DELETE','UPDATE','INSERT','ALTER TABLE')" ), - QVariantList() << mOwnerName << mTableName ) ) - { - // check grants - while ( qry.next() ) - { - QString priv = qry.value( 0 ).toString(); - - if ( priv == "DELETE" ) - { - mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures; - } - else if ( priv == "UPDATE" ) - { - mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues; - } - else if ( priv == "INSERT" ) - { - mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures; - } - else if ( priv == "ALTER TABLE" ) - { - mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes | QgsVectorDataProvider::RenameAttributes; - } - } - - if ( !mGeometryColumn.isNull() ) - { - if ( exec( qry, QString( "SELECT 1 FROM all_col_privs WHERE table_schema=? AND table_name=? AND column_name=? AND privilege='UPDATE'" ), - QVariantList() << mOwnerName << mTableName << mGeometryColumn ) ) - { - if ( qry.next() ) - mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries; - } - else - { - QgsMessageLog::logMessage( tr( "Unable to determine geometry column access privileges for column %1.%2.\nThe error message from the database was:\n%3.\nSQL: %4" ) - .arg( mQuery ) - .arg( mGeometryColumn ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), - tr( "Oracle" ) ); - } - } - } else { - QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the table %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) - .arg( mQuery ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), - tr( "Oracle" ) ); + if ( LoggedExecStatic( qry, QStringLiteral( "SELECT privilege FROM all_tab_privs WHERE table_schema=? AND table_name=? AND privilege IN ('DELETE','UPDATE','INSERT','ALTER TABLE')" ), + QVariantList() << mOwnerName << mTableName, mUri.uri() ) ) + { + // check grants + long long fetchedRows { 0 }; + while ( qry.next() ) + { + fetchedRows++; + QString priv = qry.value( 0 ).toString(); + + if ( priv == "DELETE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::DeleteFeatures; + } + else if ( priv == "UPDATE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::ChangeAttributeValues; + } + else if ( priv == "INSERT" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::AddFeatures; + } + else if ( priv == "ALTER TABLE" ) + { + mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes | QgsVectorDataProvider::RenameAttributes; + } + } + + if ( !mGeometryColumn.isNull() ) + { + + if ( LoggedExecStatic( qry, QStringLiteral( "SELECT 1 FROM all_col_privs WHERE table_schema=? AND table_name=? AND column_name=? AND privilege='UPDATE'" ), + QVariantList() << mOwnerName << mTableName << mGeometryColumn, mUri.uri() ) ) + { + if ( qry.next() ) + mEnabledCapabilities |= QgsVectorDataProvider::ChangeGeometries; + } + else + { + const QString error { tr( "Unable to determine geometry column access privileges for column %1.%2.\nThe error message from the database was:\n%3.\nSQL: %4" ) + .arg( mQuery ) + .arg( mGeometryColumn ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, + tr( "Oracle" ) ); + } + } + } + else + { + const QString error { tr( "Unable to determine table access privileges for the table %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, + tr( "Oracle" ) ); + } } } else @@ -841,11 +881,13 @@ bool QgsOracleProvider::hasSufficientPermsAndCapabilities() return false; } - if ( !exec( qry, QString( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ), QVariantList() ) ) + const QString sql { QStringLiteral( "SELECT * FROM %1 WHERE 1=0" ).arg( mQuery ) }; + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { - QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), tr( "Oracle" ) ); + const QString error { tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); return false; } } @@ -897,21 +939,25 @@ bool QgsOracleProvider::determinePrimaryKey() { mPrimaryKeyType = ( mPrimaryKeyAttrs.size() == 1 && isInt ) ? PktInt : PktFidMap; } - else if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=? AND table_name=?" ), QVariantList() << mOwnerName << mTableName ) ) - { - QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), tr( "Oracle" ) ); - } - else if ( qry.next() ) - { - // is table - QgsMessageLog::logMessage( tr( "No primary key found, using ROWID." ), tr( "Oracle" ) ); - mPrimaryKeyType = PktRowId; - } else { - determinePrimaryKeyFromUriKeyColumn(); + if ( !LoggedExecStatic( qry, QStringLiteral( "SELECT 1 FROM all_tables WHERE owner=? AND table_name=?" ), QVariantList() << mOwnerName << mTableName, mUri.uri() ) ) + { + const QString error { tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ) }; + QgsMessageLog::logMessage( error, tr( "Oracle" ) ); + } + else if ( qry.next() ) + { + // is table + QgsMessageLog::logMessage( tr( "No primary key found, using ROWID." ), tr( "Oracle" ) ); + mPrimaryKeyType = PktRowId; + } + else + { + determinePrimaryKeyFromUriKeyColumn(); + } } } else @@ -997,7 +1043,7 @@ bool QgsOracleProvider::uniqueData( QString query, QString colName ) QString sql = QString( "SELECT (SELECT count(distinct %1) FROM %2)-(SELECT count(%1) FROM %2) FROM dual" ) .arg( colName.startsWith( QLatin1String( "qgis_generated_uid_" ) ) ? colName : quotedIdentifier( colName ), mQuery ); - if ( !exec( qry, sql, QVariantList() ) || !qry.next() ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) || !qry.next() ) { QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) .arg( qry.lastError().text(), qry.lastQuery() ), tr( "Oracle" ) ); @@ -1028,7 +1074,7 @@ QVariant QgsOracleProvider::minimumValue( int index ) const QSqlQuery qry( *conn ); - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) .arg( qry.lastError().text(), qry.lastQuery() ), tr( "Oracle" ) ); @@ -1078,7 +1124,7 @@ QSet QgsOracleProvider::uniqueValues( int index, int limit ) const QSqlQuery qry( *conn ); - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) .arg( qry.lastError().text(), qry.lastQuery() ), tr( "Oracle" ) ); @@ -1120,7 +1166,7 @@ QVariant QgsOracleProvider::maximumValue( int index ) const QSqlQuery qry( *conn ); - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) .arg( qry.lastError().text(), qry.lastQuery() ), tr( "Oracle" ) ); @@ -1201,7 +1247,10 @@ QVariant QgsOracleProvider::evaluateDefaultExpression( const QString &value, con QgsOracleConn *conn = connectionRO(); QSqlQuery qry( *conn ); - if ( !exec( qry, QString( "SELECT %1 FROM dual" ).arg( value ), QVariantList() ) || !qry.next() ) + + const QString sql { QStringLiteral( "SELECT %1 FROM dual" ).arg( value ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) || !qry.next() ) { throw OracleException( tr( "Evaluation of default value failed" ), qry ); } @@ -1317,6 +1366,7 @@ bool QgsOracleProvider::addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flag insert += values + ")"; QgsDebugMsgLevel( QStringLiteral( "SQL prepare: %1" ).arg( insert ), 4 ); + if ( !ins.prepare( insert ) ) { throw OracleException( tr( "Could not prepare insert statement" ), ins ); @@ -1350,7 +1400,9 @@ bool QgsOracleProvider::addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flag } if ( !ins.exec() ) + { throw OracleException( tr( "Could not insert feature %1" ).arg( features->id() ), ins ); + } if ( !( flags & QgsFeatureSink::FastInsert ) ) { @@ -1364,7 +1416,10 @@ bool QgsOracleProvider::addFeatures( QgsFeatureList &flist, QgsFeatureSink::Flag if ( ins.lastInsertId().isValid() ) { getfid.addBindValue( QVariant( ins.lastInsertId() ) ); - if ( !getfid.exec() || !getfid.next() ) + + const bool result { getfid.exec() }; + + if ( !result || !getfid.next() ) throw OracleException( tr( "Could not retrieve feature id %1" ).arg( features->id() ), getfid ); int col = 0; @@ -1462,12 +1517,14 @@ bool QgsOracleProvider::deleteFeatures( const QgsFeatureIds &id ) for ( QgsFeatureIds::const_iterator it = id.begin(); it != id.end(); ++it ) { QVariantList args; - QString sql = QString( "DELETE FROM %1 WHERE %2" ) + QString sql = QStringLiteral( "DELETE FROM %1 WHERE %2" ) .arg( mQuery, whereClause( *it, args ) ); QgsDebugMsgLevel( "delete sql: " + sql, 2 ); - if ( !exec( qry, sql, args ) ) + if ( !LoggedExecStatic( qry, sql, args, mUri.uri() ) ) + { throw OracleException( tr( "Deletion of feature %1 failed" ).arg( *it ), qry ); + } mShared->removeFid( *it ); } @@ -1537,15 +1594,20 @@ bool QgsOracleProvider::addAttributes( const QList &attributes ) .arg( mQuery, quotedIdentifier( iter->name() ), type ); QgsDebugMsgLevel( sql, 2 ); - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) + { throw OracleException( tr( "Adding attribute %1 failed" ).arg( iter->name() ), qry ); + } if ( !iter->comment().isEmpty() ) { sql = QString( "COMMENT ON COLUMN %1.%2 IS ?" ) .arg( mQuery, quotedIdentifier( iter->name() ) ); - if ( !exec( qry, sql, QVariantList() << iter->comment() ) ) + + if ( !LoggedExecStatic( qry, sql, QVariantList() << iter->comment(), mUri.uri() ) ) + { throw OracleException( tr( "Setting comment on %1 failed" ).arg( iter->name() ), qry ); + } } qry.finish(); @@ -1609,8 +1671,10 @@ bool QgsOracleProvider::deleteAttributes( const QgsAttributeIds &ids ) .arg( mQuery, quotedIdentifier( fld.name() ) ); //send sql statement and do error handling - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) + { throw OracleException( tr( "Dropping column %1 failed" ).arg( fld.name() ), qry ); + } //delete the attribute from mAttributeFields mAttributeFields.remove( id ); @@ -1685,11 +1749,12 @@ bool QgsOracleProvider::renameAttributes( const QgsFieldNameMap &renamedAttribut for ( renameIt = renamedAttributes.constBegin(); renameIt != renamedAttributes.constEnd(); ++renameIt ) { QString src( mAttributeFields.at( renameIt.key() ).name() ); + const QString sql { QString( "ALTER TABLE %1 RENAME COLUMN %2 TO %3" ) + .arg( mQuery, + quotedIdentifier( src ), + quotedIdentifier( renameIt.value() ) ) }; - if ( !exec( qry, QString( "ALTER TABLE %1 RENAME COLUMN %2 TO %3" ) - .arg( mQuery, - quotedIdentifier( src ), - quotedIdentifier( renameIt.value() ) ), QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { throw OracleException( tr( "Renaming column %1 to %2 failed" ) .arg( quotedIdentifier( src ), @@ -1827,10 +1892,17 @@ bool QgsOracleProvider::changeAttributeValues( const QgsChangedAttributesMap &at for ( const auto &arg : std::as_const( args ) ) qry.addBindValue( arg ); + QgsDatabaseQueryLogWrapper logWrapper { sql, mUri.uri(), QStringLiteral( "oracle" ), QStringLiteral( "QgsOracleProvider" ), QGS_QUERY_LOG_ORIGIN }; + if ( !qry.exec() ) + { + logWrapper.setError( qry.lastError().text() ); + logWrapper.setQuery( QgsOracleConn::getLastExecutedQuery( qry ) ); throw OracleException( tr( "Update of feature %1 failed" ).arg( iter.key() ), qry ); + } qry.finish(); + logWrapper.setQuery( QgsOracleConn::getLastExecutedQuery( qry ) ); // update feature id map if key was changed if ( pkChanged && mPrimaryKeyType == PktFidMap ) @@ -2304,8 +2376,15 @@ bool QgsOracleProvider::changeGeometryValues( const QgsGeometryMap &geometry_map appendGeomParam( iter.value(), qry ); appendPkParams( iter.key(), qry ); + QgsDatabaseQueryLogWrapper logWrapper { update, mUri.uri(), QStringLiteral( "oracle" ), QStringLiteral( "QgsOracleProvider" ), QGS_QUERY_LOG_ORIGIN }; + if ( !qry.exec() ) + { + logWrapper.setQuery( QgsOracleConn::getLastExecutedQuery( qry ) ); + logWrapper.setError( qry.lastError().text() ); throw OracleException( tr( "Update of feature %1 failed" ).arg( iter.key() ), qry ); + } + logWrapper.setQuery( QgsOracleConn::getLastExecutedQuery( qry ) ); } qry.finish(); @@ -2359,7 +2438,9 @@ bool QgsOracleProvider::setSubsetString( const QString &theSQL, bool updateFeatu sql += "1=0"; QSqlQuery qry( *conn ); - if ( !exec( qry, sql, QVariantList() ) ) + + + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { pushError( qry.lastError().text() ); mSqlWhereClause = prevWhere; @@ -2423,10 +2504,12 @@ long long QgsOracleProvider::featureCount() const } QSqlQuery qry( *conn ); - if ( exec( qry, sql, args ) && qry.next() ) + + if ( LoggedExecStatic( qry, sql, QVariantList( ), mUri.uri() ) && qry.next() ) { mFeaturesCounted = qry.value( 0 ).toLongLong(); } + qry.finish(); QgsDebugMsgLevel( "number of features: " + QString::number( mFeaturesCounted ), 2 ); @@ -2451,15 +2534,18 @@ QgsRectangle QgsOracleProvider::extent() const if ( mUseEstimatedMetadata ) { // TODO: make SDO_DIMNAME values configurable (#16252) - if ( exec( qry, QStringLiteral( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=? AND table_name=? AND column_name=? AND sdo_dimname='X'" ), - QVariantList() << mOwnerName << mTableName << mGeometryColumn ) && + + const QString sql { QStringLiteral( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=? AND table_name=? AND column_name=? AND sdo_dimname='X'" ) }; + + if ( LoggedExecStatic( qry, sql, + QVariantList() << mOwnerName << mTableName << mGeometryColumn, mUri.uri() ) && qry.next() ) { mLayerExtent.setXMinimum( qry.value( 0 ).toDouble() ); mLayerExtent.setXMaximum( qry.value( 1 ).toDouble() ); - if ( exec( qry, QStringLiteral( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=? AND table_name=? AND column_name=? AND sdo_dimname='Y'" ), - QVariantList() << mOwnerName << mTableName << mGeometryColumn ) && + if ( LoggedExecStatic( qry, QStringLiteral( "SELECT sdo_lb,sdo_ub FROM mdsys.all_sdo_geom_metadata m, table(m.diminfo) WHERE owner=? AND table_name=? AND column_name=? AND sdo_dimname='Y'" ), + QVariantList() << mOwnerName << mTableName << mGeometryColumn, mUri.uri() ) && qry.next() ) { mLayerExtent.setYMinimum( qry.value( 0 ).toDouble() ); @@ -2471,9 +2557,10 @@ QgsRectangle QgsOracleProvider::extent() const if ( mHasSpatialIndex && mUseEstimatedMetadata ) { - ok = exec( qry, - QStringLiteral( "SELECT SDO_TUNE.EXTENT_OF(?,?) FROM dual" ), - QVariantList() << QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) << mGeometryColumn ); + const QString sql { QStringLiteral( "SELECT SDO_TUNE.EXTENT_OF(?,?) FROM dual" ) }; + ok = LoggedExecStatic( qry, + sql, + QVariantList() << QString( "%1.%2" ).arg( mOwnerName ).arg( mTableName ) << mGeometryColumn, mUri.uri() ); } } @@ -2484,7 +2571,7 @@ QgsRectangle QgsOracleProvider::extent() const if ( !mSqlWhereClause.isEmpty() ) sql += QString( " WHERE %1" ).arg( mSqlWhereClause ); - ok = exec( qry, sql, QVariantList() ); + ok = LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ); } if ( ok && qry.next() ) @@ -2533,7 +2620,9 @@ bool QgsOracleProvider::getGeometryDetails() QSqlQuery qry( *conn ); if ( mIsQuery ) { - if ( !exec( qry, QString( "SELECT %1 FROM %2 WHERE 1=0" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery ), QVariantList() ) ) + const QString sql { QStringLiteral( "SELECT %1 FROM %2 WHERE 1=0" ).arg( quotedIdentifier( mGeometryColumn ) ).arg( mQuery ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Could not execute query.\nThe error message from the database was:\n%1.\nSQL: %2" ) .arg( qry.lastError().text() ) @@ -2558,32 +2647,41 @@ bool QgsOracleProvider::getGeometryDetails() if ( !ownerName.isEmpty() ) { - if ( exec( qry, QString( "SELECT srid FROM mdsys.all_sdo_geom_metadata WHERE owner=? AND table_name=? AND column_name=?" ), - QVariantList() << ownerName << tableName << geomCol ) ) + { - if ( qry.next() ) + const QString sql {QStringLiteral( "SELECT srid FROM mdsys.all_sdo_geom_metadata WHERE owner=? AND table_name=? AND column_name=?" ) }; + + if ( LoggedExecStatic( qry, sql, + QVariantList() << ownerName << tableName << geomCol, mUri.uri() ) ) { - detectedSrid = qry.value( 0 ).toInt(); + if ( qry.next() ) + { + detectedSrid = qry.value( 0 ).toInt(); + } + else + { + QgsMessageLog::logMessage( tr( "Could not retrieve SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + .arg( mQuery ) + .arg( qry.lastError().text() ) + .arg( qry.lastQuery() ), tr( "Oracle" ) ); + } } else { - QgsMessageLog::logMessage( tr( "Could not retrieve SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) + QgsMessageLog::logMessage( tr( "Could not determine SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) .arg( mQuery ) .arg( qry.lastError().text() ) .arg( qry.lastQuery() ), tr( "Oracle" ) ); } } - else - { - QgsMessageLog::logMessage( tr( "Could not determine SRID of %1.\nThe error message from the database was:\n%2.\nSQL: %3" ) - .arg( mQuery ) - .arg( qry.lastError().text() ) - .arg( qry.lastQuery() ), tr( "Oracle" ) ); - } - if ( exec( qry, QString( mUseEstimatedMetadata - ? "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<100) WHERE rownum<=2" - : "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ), QVariantList() ) ) + QString sql { mUseEstimatedMetadata ? + QStringLiteral( "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<100) WHERE rownum<=2" ) : + QStringLiteral( "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<=2" ) }; + + sql = sql.arg( quotedIdentifier( geomCol ), mQuery ); + + if ( LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { if ( qry.next() ) { @@ -2705,30 +2803,41 @@ bool QgsOracleProvider::createSpatialIndex() // TODO: make precision configurable // TODO: make SDO_DIMNAME values configurable (#16252) QgsRectangle r( extent() ); - if ( !exec( qry, QString( "UPDATE mdsys.user_sdo_geom_metadata SET diminfo=mdsys.sdo_dim_array(" - "mdsys.sdo_dim_element('X', ?, ?, 0.001)," - "mdsys.sdo_dim_element('Y', ?, ?, 0.001)" - ") WHERE table_name=? AND column_name=?" ), - QVariantList() << r.xMinimum() << r.xMaximum() << r.yMinimum() << r.yMaximum() << mTableName << mGeometryColumn ) - ) + + const QString sql { QStringLiteral( "UPDATE mdsys.user_sdo_geom_metadata SET diminfo=mdsys.sdo_dim_array(" + "mdsys.sdo_dim_element('X', ?, ?, 0.001)," + "mdsys.sdo_dim_element('Y', ?, ?, 0.001)" + ") WHERE table_name=? AND column_name=?" ) }; + { - QgsMessageLog::logMessage( tr( "Could not update metadata for %1.%2.\nSQL: %3\nError: %4" ) - .arg( mTableName ) - .arg( mGeometryColumn ) - .arg( qry.lastQuery() ) - .arg( qry.lastError().text() ), - tr( "Oracle" ) ); - return false; + + if ( !LoggedExecStatic( qry, sql, + QVariantList() << r.xMinimum() << r.xMaximum() << r.yMinimum() << r.yMaximum() << mTableName << mGeometryColumn, mUri.uri() ) + ) + { + QgsMessageLog::logMessage( tr( "Could not update metadata for %1.%2.\nSQL: %3\nError: %4" ) + .arg( mTableName ) + .arg( mGeometryColumn ) + .arg( qry.lastQuery() ) + .arg( qry.lastError().text() ), + tr( "Oracle" ) ); + return false; + } + } if ( qry.numRowsAffected() == 0 ) { - if ( !exec( qry, QString( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (?,?,?,mdsys.sdo_dim_array(" - "mdsys.sdo_dim_element('X', ?, ?, 0.001)," - "mdsys.sdo_dim_element('Y', ?, ?, 0.001)" - "))" ), - QVariantList() << mTableName << mGeometryColumn << ( mSrid < 1 ? QVariant( QVariant::Int ) : mSrid ) - << r.xMinimum() << r.xMaximum() << r.yMinimum() << r.yMaximum() ) + + const QString sql { QStringLiteral( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (?,?,?,mdsys.sdo_dim_array(" + "mdsys.sdo_dim_element('X', ?, ?, 0.001)," + "mdsys.sdo_dim_element('Y', ?, ?, 0.001)" + "))" ) }; + + + if ( !LoggedExecStatic( qry, sql, + QVariantList() << mTableName << mGeometryColumn << ( mSrid < 1 ? QVariant( QVariant::Int ) : mSrid ) + << r.xMinimum() << r.xMaximum() << r.yMinimum() << r.yMaximum(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Could not insert metadata for %1.%2.\nSQL: %3\nError: %4" ) @@ -2756,7 +2865,10 @@ bool QgsOracleProvider::createSpatialIndex() } else { - if ( !exec( qry, QString( "ALTER INDEX %1 REBUILD" ).arg( mSpatialIndexName ), QVariantList() ) ) + + const QString sql { QStringLiteral( "ALTER INDEX %1 REBUILD" ).arg( mSpatialIndexName ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), mUri.uri() ) ) { QgsMessageLog::logMessage( tr( "Rebuild of spatial index failed.\nSQL: %1\nError: %2" ) .arg( qry.lastQuery() ) @@ -2928,11 +3040,15 @@ Qgis::VectorExportResult QgsOracleProvider::createEmptyLayer( throw OracleException( tr( "Could not start transaction" ), db ); } - if ( !exec( qry, QString( "SELECT 1 FROM all_tables WHERE owner=? AND table_name=?" ), - QVariantList() << ownerName << tableName - ) ) { - throw OracleException( tr( "Could not determine table existence." ), qry ); + const QString sql { QStringLiteral( "SELECT 1 FROM all_tables WHERE owner=? AND table_name=?" ) }; + + if ( !LoggedExecStatic( qry, sql, + QVariantList() << ownerName << tableName, uri ) ) + { + throw OracleException( tr( "Could not determine table existence." ), qry ); + } + } bool exists = qry.next(); @@ -2942,7 +3058,11 @@ Qgis::VectorExportResult QgsOracleProvider::createEmptyLayer( if ( overwrite ) { // delete the table if exists, then re-create it - if ( !exec( qry, QString( "DROP TABLE %1" ).arg( ownerTableName ), QVariantList() ) ) + + const QString sql { QStringLiteral( "DROP TABLE %1" ).arg( ownerTableName ) }; + + + if ( !LoggedExecStatic( qry, sql, QVariantList(), uri ) ) { throw OracleException( tr( "Table %1 could not be dropped." ).arg( ownerTableName ), qry ); } @@ -2953,7 +3073,7 @@ Qgis::VectorExportResult QgsOracleProvider::createEmptyLayer( } } - QString sql = QString( "CREATE TABLE %1(" ).arg( ownerTableName ); + QString sql = QStringLiteral( "CREATE TABLE %1(" ).arg( ownerTableName ); QString delim; if ( hasPrimaryKey ) @@ -2971,7 +3091,7 @@ Qgis::VectorExportResult QgsOracleProvider::createEmptyLayer( sql += ")"; - if ( !exec( qry, sql, QVariantList() ) ) + if ( !LoggedExecStatic( qry, sql, QVariantList(), uri ) ) { throw OracleException( tr( "Table creation failed." ), qry ); } @@ -2999,7 +3119,10 @@ Qgis::VectorExportResult QgsOracleProvider::createEmptyLayer( if ( created ) { - if ( !exec( qry, QString( "DROP TABLE %1" ).arg( ownerTableName ), QVariantList() ) ) + + const QString sql { QStringLiteral( "DROP TABLE %1" ).arg( ownerTableName ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), uri ) ) { QgsMessageLog::logMessage( tr( "Drop created table %1 failed.\nSQL: %2\nError: %3" ) .arg( qry.lastQuery() ) @@ -3171,9 +3294,16 @@ void QgsOracleProvider::insertGeomMetadata( QgsOracleConn *conn, const QString & QgsDebugMsgLevel( QStringLiteral( "%1:%2 not found in mdsys.cs_srs - trying WKT" ).arg( parts[0] ).arg( parts[1] ), 2 ); QString wkt = srs.toWkt(); - if ( !exec( qry, QStringLiteral( "SELECT srid FROM mdsys.cs_srs WHERE wktext=?" ), QVariantList() << wkt ) ) + + const QString sql { QStringLiteral( "SELECT srid FROM mdsys.cs_srs WHERE wktext=?" ) }; + { - throw OracleException( tr( "Could not lookup WKT." ), qry ); + + if ( !LoggedExecStatic( qry, sql, QVariantList() << wkt, conn->connInfo() ) ) + { + throw OracleException( tr( "Could not lookup WKT." ), qry ); + } + } if ( qry.next() ) @@ -3182,17 +3312,25 @@ void QgsOracleProvider::insertGeomMetadata( QgsOracleConn *conn, const QString & } else { - if ( !exec( qry, QStringLiteral( "SELECT max(srid)+1 FROM sdo_coord_ref_system" ), QVariantList() ) || !qry.next() ) + { - throw OracleException( tr( "Could not determine new srid." ), qry ); + const QString sql { QStringLiteral( "SELECT max(srid)+1 FROM sdo_coord_ref_system" ) }; + + if ( !LoggedExecStatic( qry, sql, QVariantList(), conn->connInfo() ) || !qry.next() ) + { + throw OracleException( tr( "Could not determine new srid." ), qry ); + } } srid = qry.value( 0 ).toInt(); - if ( !exec( qry, QStringLiteral( "INSERT" - " INTO sdo_coord_ref_system(srid,coord_ref_sys_name,coord_ref_sys_kind,legacy_wktext,is_valid,is_legacy,information_source)" - " VALUES (?,?,?,?,'TRUE','TRUE','GDAL/OGR via QGIS')" ), - QVariantList() << srid << srs.description() << ( srs.isGeographic() ? "GEOGRAPHIC2D" : "PROJECTED" ) << wkt ) ) + const QString sql { QStringLiteral( "INSERT" + " INTO sdo_coord_ref_system(srid,coord_ref_sys_name,coord_ref_sys_kind,legacy_wktext,is_valid,is_legacy,information_source)" + " VALUES (?,?,?,?,'TRUE','TRUE','GDAL/OGR via QGIS')" ) }; + + + if ( !LoggedExecStatic( qry, sql, + QVariantList() << srid << srs.description() << ( srs.isGeographic() ? "GEOGRAPHIC2D" : "PROJECTED" ) << wkt, conn->connInfo() ) ) { throw OracleException( tr( "CRS not found and could not be created." ), qry ); } @@ -3203,8 +3341,10 @@ void QgsOracleProvider::insertGeomMetadata( QgsOracleConn *conn, const QString & throw OracleException( tr( "Cannot insert geometry metadata for table '%1' and geometry column '%2'. Both needs to be uppercase" ).arg( tableName, geometryColumn ), qry ); - if ( !exec( qry, QStringLiteral( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (?,?,?,%1)" ).arg( diminfo ), - QVariantList() << tableName.toUpper() << geometryColumn.toUpper() << srid ) ) + const QString sql { QStringLiteral( "INSERT INTO mdsys.user_sdo_geom_metadata(table_name,column_name,srid,diminfo) VALUES (?,?,?,%1)" ).arg( diminfo ) }; + + if ( !LoggedExecStatic( qry, sql, + QVariantList() << tableName.toUpper() << geometryColumn.toUpper() << srid, conn->connInfo() ) ) { throw OracleException( tr( "Could not insert metadata." ), qry ); } @@ -3217,7 +3357,7 @@ QgsCoordinateReferenceSystem QgsOracleProvider::lookupCrs( QgsOracleConn *conn, QSqlQuery qry( *conn ); // apparently some EPSG codes don't have the auth_name setup in cs_srs - if ( exec( qry, QString( "SELECT coalesce(auth_name,'EPSG'),auth_srid,wktext FROM mdsys.cs_srs WHERE srid=?" ), QVariantList() << srsid ) ) + if ( LoggedExecStatic( qry, QString( "SELECT coalesce(auth_name,'EPSG'),auth_srid,wktext FROM mdsys.cs_srs WHERE srid=?" ), QVariantList() << srsid, conn->connInfo() ) ) { if ( qry.next() ) { diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h index c79bb978087..230bf4bb25b 100644 --- a/src/providers/oracle/qgsoracleprovider.h +++ b/src/providers/oracle/qgsoracleprovider.h @@ -50,6 +50,7 @@ enum QgsOraclePrimaryKeyType PktFidMap }; + /** * \class QgsOracleProvider * \brief Data provider for oracle layers. @@ -173,7 +174,7 @@ class QgsOracleProvider final: public QgsVectorDataProvider QString description() const override; QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const override; - static bool exec( QSqlQuery &qry, QString sql, const QVariantList &args ); + static bool execLoggedStatic( QSqlQuery &qry, const QString &sql, const QVariantList &args, const QString &uri, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); bool isSaveAndLoadStyleToDatabaseSupported() const override { return true; } void setTransaction( QgsTransaction *transaction ) override; diff --git a/src/providers/oracle/qgsoracleproviderconnection.cpp b/src/providers/oracle/qgsoracleproviderconnection.cpp index d9d4297940b..b8f7ec5851c 100644 --- a/src/providers/oracle/qgsoracleproviderconnection.cpp +++ b/src/providers/oracle/qgsoracleproviderconnection.cpp @@ -1365,15 +1365,22 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsOracleProviderConnection:: QSqlQuery qry( *pconn.get() ); std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + + QgsDatabaseQueryLogWrapper logWrapper { sql, uri(), providerKey(), QStringLiteral( "QgsAbstractDatabaseProviderConnection" ), QGS_QUERY_LOG_ORIGIN }; + if ( !qry.exec( sql ) ) { + logWrapper.setError( qry.lastError().text() ); throw QgsProviderConnectionException( QObject::tr( "SQL error: %1 returned %2" ) .arg( qry.lastQuery(), qry.lastError().text() ) ); } if ( feedback && feedback->isCanceled() ) + { + logWrapper.setCanceled(); return QgsAbstractDatabaseProviderConnection::QueryResult(); + } if ( qry.isActive() ) { diff --git a/src/providers/oracle/qgsoracletablemodel.cpp b/src/providers/oracle/qgsoracletablemodel.cpp index 3c5be08ef56..c8df22dca62 100644 --- a/src/providers/oracle/qgsoracletablemodel.cpp +++ b/src/providers/oracle/qgsoracletablemodel.cpp @@ -61,6 +61,7 @@ bool QgsOracleTableModel::searchableColumn( int column ) const case DbtmSelectAtId: return false; } + return false; } void QgsOracleTableModel::addTableEntry( const QgsOracleLayerProperty &layerProperty ) diff --git a/src/providers/oracle/qgsoracletransaction.cpp b/src/providers/oracle/qgsoracletransaction.cpp index 0a6f666d762..c218c96b530 100644 --- a/src/providers/oracle/qgsoracletransaction.cpp +++ b/src/providers/oracle/qgsoracletransaction.cpp @@ -73,7 +73,13 @@ bool QgsOracleTransaction::executeSql( const QString &sql, QString &errorMsg, bo } QgsDebugMsg( QStringLiteral( "Transaction sql: %1" ).arg( sql ) ); + + QgsDatabaseQueryLogWrapper logWrapper { sql, mConnString, QStringLiteral( "oracle" ), QStringLiteral( "QgsOracleConn" ), QGS_QUERY_LOG_ORIGIN }; const bool res = mConn->exec( sql, true, &errorMsg ); + if ( ! errorMsg.isEmpty() ) + { + logWrapper.setError( errorMsg ); + } if ( !res ) { if ( isDirty ) diff --git a/src/providers/postgres/qgspostgresconn.cpp b/src/providers/postgres/qgspostgresconn.cpp index edd52477421..3be38308650 100644 --- a/src/providers/postgres/qgspostgresconn.cpp +++ b/src/providers/postgres/qgspostgresconn.cpp @@ -31,6 +31,7 @@ #include "qgspostgresstringutils.h" #include "qgspostgresconnpool.h" #include "qgsvariantutils.h" +#include "qgsdbquerylog.h" #include #include @@ -412,8 +413,8 @@ 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" ) ); + LoggedPQexecNR( "QgsPostgresConn", QStringLiteral( "SET application_name='QGIS'" ) ); + LoggedPQexecNR( "QgsPostgresConn", QStringLiteral( "SET extra_float_digits=3" ) ); } PQsetNoticeProcessor( mConn, noticeProcessor, nullptr ); @@ -498,7 +499,7 @@ void QgsPostgresConn::addColumnInfo( QgsPostgresLayerProperty &layerProperty, co .arg( quotedIdentifier( schemaName ), quotedIdentifier( viewName ) ); QgsDebugMsgLevel( "getting column info: " + sql, 2 ); - QgsPostgresResult colRes( PQexec( sql ) ); + QgsPostgresResult colRes( LoggedPQexec( "QgsPostgresConn", sql ) ); layerProperty.pkCols.clear(); layerProperty.nSpCols = 0; @@ -656,7 +657,7 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP QgsDebugMsgLevel( "getting table info from layer registries: " + query, 2 ); - result = PQexec( query, true ); + result = LoggedPQexec( "QgsPostgresConn", query ); // NOTE: we intentionally continue if the query fails // (for example because PostGIS is not installed) for ( int idx = 0; idx < result.PQntuples(); idx++ ) @@ -810,14 +811,16 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP QgsDebugMsgLevel( "getting spatial table info from pg_catalog: " + sql, 2 ); - result = PQexec( sql ); + + + result = LoggedPQexec( QStringLiteral( "QgsPostresConn" ), sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { 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" ) ); + LoggedPQexecNR( "QgsPostgresConn", QStringLiteral( "COMMIT" ) ); return false; } @@ -922,7 +925,7 @@ bool QgsPostgresConn::getTableInfo( bool searchGeometryColumnsOnly, bool searchP QgsDebugMsgLevel( "getting non-spatial table info: " + sql, 2 ); - result = PQexec( sql ); + result = LoggedPQexec( QStringLiteral( "QgsPostresConn" ), sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { @@ -1018,10 +1021,10 @@ bool QgsPostgresConn::getSchemas( QList &schemas ) QString sql = QStringLiteral( "SELECT nspname, pg_get_userbyid(nspowner), pg_catalog.obj_description(oid) FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema' ORDER BY nspname" ); - result = PQexec( sql, true ); + result = LoggedPQexec( QStringLiteral( "QgsPostresConn" ), sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { - PQexecNR( QStringLiteral( "COMMIT" ) ); + LoggedPQexecNR( "QgsPostgresConn", QStringLiteral( "COMMIT" ) ); return false; } @@ -1084,7 +1087,7 @@ QString QgsPostgresConn::postgisVersion() const mPostgresqlVersion = PQserverVersion( mConn ); - QgsPostgresResult result( PQexec( QStringLiteral( "SELECT postgis_version()" ), false ) ); + QgsPostgresResult result( LoggedPQexecNoLogError( QStringLiteral( "QgsPostgresConn" ), QStringLiteral( "SELECT postgis_version()" ) ) ); if ( result.PQntuples() != 1 ) { QgsMessageLog::logMessage( tr( "No PostGIS support in the database." ), tr( "PostGIS" ) ); @@ -1121,7 +1124,7 @@ QString QgsPostgresConn::postgisVersion() const // apparently PostGIS 1.5.2 doesn't report capabilities in postgis_version() anymore if ( mPostgisVersionMajor > 1 || ( mPostgisVersionMajor == 1 && mPostgisVersionMinor >= 5 ) ) { - result = PQexec( QStringLiteral( "SELECT postgis_geos_version(), postgis_proj_version()" ) ); + result = LoggedPQexec( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "SELECT postgis_geos_version(), postgis_proj_version()" ) ); mGeosAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 0 ); mProjAvailable = result.PQntuples() == 1 && !result.PQgetisnull( 0, 1 ); QgsDebugMsgLevel( QStringLiteral( "geos:%1 proj:%2" ) @@ -1146,19 +1149,18 @@ QString QgsPostgresConn::postgisVersion() const mTopologyAvailable = false; if ( mPostgisVersionMajor > 1 ) { - QgsPostgresResult result( - PQexec( - QStringLiteral( - "SELECT has_schema_privilege(n.oid, 'usage')" - " AND has_table_privilege(t.oid, 'select')" - " AND has_table_privilege(l.oid, 'select')" - " FROM pg_namespace n, pg_class t, pg_class l" - " WHERE n.nspname = 'topology'" - " AND t.relnamespace = n.oid" - " AND l.relnamespace = n.oid" - " AND t.relname = 'topology'" - " AND l.relname = 'layer'" - ) ) ); + const QString query = QStringLiteral( + "SELECT has_schema_privilege(n.oid, 'usage')" + " AND has_table_privilege(t.oid, 'select')" + " AND has_table_privilege(l.oid, 'select')" + " FROM pg_namespace n, pg_class t, pg_class l" + " WHERE n.nspname = 'topology'" + " AND t.relnamespace = n.oid" + " AND l.relnamespace = n.oid" + " AND t.relname = 'topology'" + " AND l.relname = 'layer'" + ); + QgsPostgresResult result( LoggedPQexec( QStringLiteral( "QgsPostresConn" ), query ) ); if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) { mTopologyAvailable = true; @@ -1179,17 +1181,17 @@ QString QgsPostgresConn::postgisVersion() const if ( mPostgresqlVersion >= 90000 ) { QgsDebugMsgLevel( QStringLiteral( "Checking for pointcloud support" ), 2 ); - result = PQexec( QStringLiteral( - "SELECT has_table_privilege(c.oid, 'select')" - " AND has_table_privilege(f.oid, 'select')" - " FROM pg_class c, pg_class f, pg_namespace n, pg_extension e" - " WHERE c.relnamespace = n.oid" - " AND c.relname = 'pointcloud_columns'" - " AND f.relnamespace = n.oid" - " AND f.relname = 'pointcloud_formats'" - " AND n.oid = e.extnamespace" - " AND e.extname = 'pointcloud'" - ), false ); + result = LoggedPQexecNoLogError( QStringLiteral( "QgsPostresConn" ), QStringLiteral( + "SELECT has_table_privilege(c.oid, 'select')" + " AND has_table_privilege(f.oid, 'select')" + " FROM pg_class c, pg_class f, pg_namespace n, pg_extension e" + " WHERE c.relnamespace = n.oid" + " AND c.relname = 'pointcloud_columns'" + " AND f.relnamespace = n.oid" + " AND f.relname = 'pointcloud_formats'" + " AND n.oid = e.extnamespace" + " AND e.extname = 'pointcloud'" + ) ); if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) { mPointcloudAvailable = true; @@ -1200,14 +1202,14 @@ QString QgsPostgresConn::postgisVersion() const QgsDebugMsgLevel( QStringLiteral( "Checking for raster support" ), 2 ); if ( mPostgisVersionMajor >= 2 ) { - result = PQexec( QStringLiteral( - "SELECT has_table_privilege(c.oid, 'select')" - " FROM pg_class c, pg_namespace n, pg_type t" - " WHERE c.relnamespace = n.oid" - " AND n.oid = t.typnamespace" - " AND c.relname = 'raster_columns'" - " AND t.typname = 'raster'" - ), false ); + result = LoggedPQexecNoLogError( QStringLiteral( "QgsPostresConn" ), QStringLiteral( + "SELECT has_table_privilege(c.oid, 'select')" + " FROM pg_class c, pg_namespace n, pg_type t" + " WHERE c.relnamespace = n.oid" + " AND n.oid = t.typnamespace" + " AND c.relname = 'raster_columns'" + " AND t.typname = 'raster'" + ) ); if ( result.PQntuples() >= 1 && result.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) { mRasterAvailable = true; @@ -1327,12 +1329,14 @@ QString QgsPostgresConn::quotedJsonValue( const QVariant &value ) return quotedString( QString::fromStdString( j.dump() ) ); } -PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool retry ) const +PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool retry, const QString &originatorClass, const QString &queryOrigin ) const { QMutexLocker locker( &mLock ); QgsDebugMsgLevel( QStringLiteral( "Executing SQL: %1" ).arg( query ), 3 ); + std::unique_ptr logWrapper = std::make_unique( query, mConnInfo, QStringLiteral( "postgres" ), originatorClass, queryOrigin ); + PGresult *res = ::PQexec( mConn, query.toUtf8() ); // libpq may return a non null ptr with conn status not OK so we need to check for it to allow a retry below @@ -1341,11 +1345,13 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool ret int errorStatus = PQresultStatus( res ); if ( errorStatus != PGRES_COMMAND_OK && errorStatus != PGRES_TUPLES_OK ) { + const QString error { tr( "Erroneous query: %1 returned %2 [%3]" ) + .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ) }; + logWrapper->setError( error ); + if ( logError ) { - QgsMessageLog::logMessage( tr( "Erroneous query: %1 returned %2 [%3]" ) - .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ), - tr( "PostGIS" ) ); + QgsMessageLog::logMessage( error, tr( "PostGIS" ) ); } else { @@ -1353,14 +1359,17 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool ret .arg( query ).arg( errorStatus ).arg( PQresultErrorMessage( res ) ) ); } } + logWrapper->setFetchedRows( PQntuples( res ) ); return res; } if ( PQstatus() != CONNECTION_OK ) { + const QString error { tr( "Connection error: %1 returned %2 [%3]" ) + .arg( query ).arg( PQstatus() ).arg( PQerrorMessage() ) }; + logWrapper->setError( error ); if ( logError ) { - QgsMessageLog::logMessage( tr( "Connection error: %1 returned %2 [%3]" ) - .arg( query ).arg( PQstatus() ).arg( PQerrorMessage() ), + QgsMessageLog::logMessage( error, tr( "PostGIS" ) ); } else @@ -1371,9 +1380,11 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool ret } else { + const QString error { tr( "Query failed: %1\nError: no result buffer" ).arg( query ) }; + logWrapper->setError( error ); if ( logError ) { - QgsMessageLog::logMessage( tr( "Query failed: %1\nError: no result buffer" ).arg( query ), tr( "PostGIS" ) ); + QgsMessageLog::logMessage( error, tr( "PostGIS" ) ); } else { @@ -1385,6 +1396,7 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool ret { QgsMessageLog::logMessage( tr( "resetting bad connection." ), tr( "PostGIS" ) ); ::PQreset( mConn ); + logWrapper.reset( new QgsDatabaseQueryLogWrapper( query, mConnInfo, QStringLiteral( "postgres" ), originatorClass, queryOrigin ) ); res = PQexec( query, logError, false ); if ( PQstatus() == CONNECTION_OK ) { @@ -1395,13 +1407,17 @@ PGresult *QgsPostgresConn::PQexec( const QString &query, bool logError, bool ret } else { - QgsMessageLog::logMessage( tr( "retry after reset failed again." ), tr( "PostGIS" ) ); + const QString error { tr( "retry after reset failed again." ) }; + logWrapper->setError( error ); + QgsMessageLog::logMessage( error, tr( "PostGIS" ) ); return nullptr; } } else { - QgsMessageLog::logMessage( tr( "connection still bad after reset." ), tr( "PostGIS" ) ); + const QString error { tr( "connection still bad after reset." ) }; + logWrapper->setError( error ); + QgsMessageLog::logMessage( error, tr( "PostGIS" ) ); } } else @@ -1442,8 +1458,8 @@ 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 ) ); + return LoggedPQexecNR( "QgsPostgresConn", QStringLiteral( "%1DECLARE %2 BINARY CURSOR%3 FOR %4" ). + arg( preStr, cursorName, !mTransaction ? QString() : QStringLiteral( " WITH HOLD" ), sql ) ); } bool QgsPostgresConn::closeCursor( const QString &cursorName ) @@ -1457,7 +1473,7 @@ bool QgsPostgresConn::closeCursor( const QString &cursorName ) postStr = QStringLiteral( ";COMMIT" ); } - if ( !PQexecNR( QStringLiteral( "CLOSE %1%2" ).arg( cursorName, postStr ) ) ) + if ( !LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "CLOSE %1%2" ).arg( cursorName, postStr ) ) ) return false; return true; @@ -1469,11 +1485,11 @@ QString QgsPostgresConn::uniqueCursorName() return QStringLiteral( "qgis_%1" ).arg( ++mNextCursorId ); } -bool QgsPostgresConn::PQexecNR( const QString &query ) +bool QgsPostgresConn::PQexecNR( const QString &query, const QString &originatorClass, const QString &queryOrigin ) { QMutexLocker locker( &mLock ); // to protect access to mOpenCursors - QgsPostgresResult res( PQexec( query, false ) ); + QgsPostgresResult res( PQexec( query, false, true, originatorClass, queryOrigin ) ); ExecStatusType errorStatus = res.PQresultStatus(); if ( errorStatus == PGRES_COMMAND_OK ) @@ -1495,7 +1511,7 @@ bool QgsPostgresConn::PQexecNR( const QString &query ) if ( PQstatus() == CONNECTION_OK ) { - PQexecNR( QStringLiteral( "ROLLBACK" ) ); + LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "ROLLBACK" ) ); } return false; @@ -1575,11 +1591,11 @@ bool QgsPostgresConn::begin() QMutexLocker locker( &mLock ); if ( mTransaction ) { - return PQexecNR( QStringLiteral( "SAVEPOINT transaction_savepoint" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "SAVEPOINT transaction_savepoint" ) ); } else { - return PQexecNR( QStringLiteral( "BEGIN" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "BEGIN" ) ); } } @@ -1588,11 +1604,11 @@ bool QgsPostgresConn::commit() QMutexLocker locker( &mLock ); if ( mTransaction ) { - return PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) ); } else { - return PQexecNR( QStringLiteral( "COMMIT" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "COMMIT" ) ); } } @@ -1601,12 +1617,12 @@ bool QgsPostgresConn::rollback() QMutexLocker locker( &mLock ); if ( mTransaction ) { - return PQexecNR( QStringLiteral( "ROLLBACK TO SAVEPOINT transaction_savepoint" ) ) - && PQexecNR( QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "ROLLBACK TO SAVEPOINT transaction_savepoint" ) ) + && LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "RELEASE SAVEPOINT transaction_savepoint" ) ); } else { - return PQexecNR( QStringLiteral( "ROLLBACK" ) ); + return LoggedPQexecNR( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "ROLLBACK" ) ); } } @@ -1892,7 +1908,7 @@ void QgsPostgresConn::deduceEndian() .arg( queryCounter ) .arg( errorCounter ), 2 ); - QgsPostgresResult res( PQexec( QStringLiteral( "select regclass('pg_class')::oid" ) ) ); + QgsPostgresResult res( LoggedPQexec( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "select regclass('pg_class')::oid" ) ) ); QString oidValue = res.PQgetvalue( 0, 0 ); QgsDebugMsgLevel( QStringLiteral( "Creating binary cursor" ), 2 ); @@ -1902,7 +1918,7 @@ void QgsPostgresConn::deduceEndian() QgsDebugMsgLevel( QStringLiteral( "Fetching a record and attempting to get check endian-ness" ), 2 ); - res = PQexec( QStringLiteral( "fetch forward 1 from oidcursor" ) ); + res = LoggedPQexec( QStringLiteral( "QgsPostresConn" ), QStringLiteral( "fetch forward 1 from oidcursor" ) ); mSwapEndian = true; if ( res.PQntuples() > 0 ) @@ -2084,7 +2100,7 @@ void QgsPostgresConn::retrieveLayerTypes( QVector &l QgsDebugMsgLevel( "Layer types,srids and dims query: " + query, 3 ); - QgsPostgresResult res( PQexec( query ) ); + QgsPostgresResult res( LoggedPQexec( QStringLiteral( "QgsPostresConn" ), query ) ); if ( res.PQresultStatus() != PGRES_TUPLES_OK ) { // TODO: print some error here ? @@ -2678,7 +2694,7 @@ QString QgsPostgresConn::currentDatabase() const QMutexLocker locker( &mLock ); QString database; QString sql = "SELECT current_database()"; - QgsPostgresResult res( PQexec( sql ) ); + QgsPostgresResult res( LoggedPQexec( QStringLiteral( "QgsPostresConn" ), sql ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) { diff --git a/src/providers/postgres/qgspostgresconn.h b/src/providers/postgres/qgspostgresconn.h index 50b25511d84..57ce301c33f 100644 --- a/src/providers/postgres/qgspostgresconn.h +++ b/src/providers/postgres/qgspostgresconn.h @@ -200,6 +200,12 @@ class QgsPoolPostgresConn class QgsPostgresConn *get() const { return mPgConn; } }; +#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(_class, query) PQexecNR( query, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) +#define LoggedPQexec(_class, query) PQexec( query, true, true, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) +#define LoggedPQexecNoLogError(_class, query ) PQexec( query, false, true, _class, QString(QString( __FILE__ ).mid( sPostgresConQueryLogFilePrefixLength ) + ':' + QString::number( __LINE__ ) + " (" + __FUNCTION__ + ")") ) + class QgsPostgresConn : public QObject { Q_OBJECT @@ -247,7 +253,7 @@ class QgsPostgresConn : public QObject int pgVersion() const { return mPostgresqlVersion; } //! run a query and free result buffer - bool PQexecNR( const QString &query ); + bool PQexecNR( const QString &query, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ); //! cursor handling bool openCursor( const QString &cursorName, const QString &declare ); @@ -264,7 +270,7 @@ class QgsPostgresConn : public QObject // // run a query and check for errors, thread-safe - PGresult *PQexec( const QString &query, bool logError = true, bool retry = true ) const; + PGresult *PQexec( const QString &query, bool logError = true, bool retry = true, const QString &originatorClass = QString(), const QString &queryOrigin = QString() ) const; int PQCancel(); void PQfinish(); QString PQerrorMessage() const; diff --git a/src/providers/postgres/qgspostgresdataitemguiprovider.cpp b/src/providers/postgres/qgspostgresdataitemguiprovider.cpp index bfd9c6c6f66..f10b5767362 100644 --- a/src/providers/postgres/qgspostgresdataitemguiprovider.cpp +++ b/src/providers/postgres/qgspostgresdataitemguiprovider.cpp @@ -252,7 +252,7 @@ void QgsPostgresDataItemGuiProvider::createSchema( QgsDataItem *item, QgsDataIte //create the schema const QString sql = QStringLiteral( "CREATE SCHEMA %1" ).arg( QgsPostgresConn::quotedIdentifier( schemaName ) ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { notify( tr( "New Schema" ), tr( "Unable to create schema '%1'\n%2" ).arg( schemaName, @@ -284,7 +284,7 @@ void QgsPostgresDataItemGuiProvider::deleteSchema( QgsPGSchemaItem *schemaItem, } const QString sql = QStringLiteral( "SELECT table_name FROM information_schema.tables WHERE table_schema='%1'" ).arg( schemaItem->name() ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { notify( tr( "Delete Schema" ), tr( "Unable to delete schema." ), context, Qgis::MessageLevel::Warning ); @@ -360,7 +360,7 @@ void QgsPostgresDataItemGuiProvider::renameSchema( QgsPGSchemaItem *schemaItem, const QString sql = QStringLiteral( "ALTER SCHEMA %1 RENAME TO %2" ) .arg( schemaName, QgsPostgresConn::quotedIdentifier( dlg.name() ) ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { notify( tr( "Rename Schema" ), tr( "Unable to rename schema '%1'\n%2" ).arg( schemaItem->name(), @@ -418,7 +418,7 @@ void QgsPostgresDataItemGuiProvider::renameLayer( QgsPGLayerItem *layerItem, Qgs sql = QStringLiteral( "ALTER TABLE %1 RENAME TO %2" ).arg( oldName, newName ); } - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { notify( tr( "Rename %1" ).arg( typeName ), tr( "Unable to rename '%1' %2\n%3" ).arg( lowerTypeName, layerItem->name(), @@ -463,7 +463,7 @@ void QgsPostgresDataItemGuiProvider::truncateTable( QgsPGLayerItem *layerItem, Q const QString sql = QStringLiteral( "TRUNCATE TABLE %1" ).arg( tableRef ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { notify( tr( "Truncate Table" ), tr( "Unable to truncate '%1'\n%2" ).arg( tableName, @@ -503,7 +503,7 @@ void QgsPostgresDataItemGuiProvider::refreshMaterializedView( QgsPGLayerItem *la const QString sql = QStringLiteral( "REFRESH MATERIALIZED VIEW CONCURRENTLY %1" ).arg( tableRef ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresDataItemGuiProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { notify( tr( "Refresh View" ), tr( "Unable to refresh the view '%1'\n%2" ).arg( tableRef, diff --git a/src/providers/postgres/qgspostgresdataitems.cpp b/src/providers/postgres/qgspostgresdataitems.cpp index 66384a75f34..8b8c4cdb74e 100644 --- a/src/providers/postgres/qgspostgresdataitems.cpp +++ b/src/providers/postgres/qgspostgresdataitems.cpp @@ -34,7 +34,7 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause ) { - QgsDebugMsgLevel( "deleting layer " + uri, 2 ); + QgsDebugMsg( "deleting layer " + uri ); QgsDataSourceUri dsUri( uri ); QString schemaName = dsUri.schema(); @@ -58,12 +58,12 @@ 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 ) ); - QgsPostgresResult resViewCheck( conn->PQexec( sqlViewCheck ) ); + QgsPostgresResult resViewCheck( conn->LoggedPQexec( "QgsPostgresUtils", 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 ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { errCause = QObject::tr( "Unable to delete view %1: \n%2" ) @@ -85,7 +85,7 @@ 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 ) ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) @@ -113,7 +113,7 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause ) QgsPostgresConn::quotedValue( tableName ) ); } - result = conn->PQexec( sql ); + result = conn->LoggedPQexec( "QgsPostgresUtils", sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { errCause = QObject::tr( "Unable to delete layer %1: \n%2" ) @@ -129,7 +129,7 @@ bool QgsPostgresUtils::deleteLayer( const QString &uri, QString &errCause ) bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceUri &uri, QString &errCause, bool cascade ) { - QgsDebugMsgLevel( "deleting schema " + schema, 2 ); + QgsDebugMsg( "deleting schema " + schema ); if ( schema.isEmpty() ) return false; @@ -147,7 +147,7 @@ bool QgsPostgresUtils::deleteSchema( const QString &schema, const QgsDataSourceU QString sql = QStringLiteral( "DROP SCHEMA %1 %2" ) .arg( schemaName, cascade ? QStringLiteral( "CASCADE" ) : QString() ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresUtils", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { errCause = QObject::tr( "Unable to delete schema %1: \n%2" ) @@ -262,7 +262,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 ); + QgsDebugMsg( "URI " + uri.uri( false ) ); if ( !toSchema.isNull() ) { @@ -365,7 +365,7 @@ QString QgsPGLayerItem::createUri() if ( uri.wkbType() != QgsWkbTypes::NoGeometry && mLayerProperty.srids.at( 0 ) != std::numeric_limits::min() ) uri.setSrid( QString::number( mLayerProperty.srids.at( 0 ) ) ); - QgsDebugMsgLevel( QStringLiteral( "layer uri: %1" ).arg( uri.uri( false ) ), 2 ); + QgsDebugMsg( QStringLiteral( "layer uri: %1" ).arg( uri.uri( false ) ) ); return uri.uri( false ); } diff --git a/src/providers/postgres/qgspostgresfeatureiterator.cpp b/src/providers/postgres/qgspostgresfeatureiterator.cpp index ee00f330c0e..0ba177dc04b 100644 --- a/src/providers/postgres/qgspostgresfeatureiterator.cpp +++ b/src/providers/postgres/qgspostgresfeatureiterator.cpp @@ -19,6 +19,7 @@ #include "qgspostgresprovider.h" #include "qgspostgrestransaction.h" #include "qgslogger.h" +#include "qgsdbquerylog.h" #include "qgsmessagelog.h" #include "qgssettings.h" #include "qgsexception.h" @@ -292,12 +293,18 @@ bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature &feature ) QgsDebugMsgLevel( QStringLiteral( "fetching %1 features." ).arg( mFeatureQueueSize ), 4 ); lock(); + + QgsDatabaseQueryLogWrapper logWrapper { fetch, mSource->mConnInfo, QStringLiteral( "postgres" ), QStringLiteral( "QgsPostgresFeatureIterator" ), QGS_QUERY_LOG_ORIGIN }; + if ( mConn->PQsendQuery( fetch ) == 0 ) // fetch features asynchronously { - QgsMessageLog::logMessage( QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ), QObject::tr( "PostGIS" ) ); + const QString error { QObject::tr( "Fetching from cursor %1 failed\nDatabase error: %2" ).arg( mCursorName, mConn->PQerrorMessage() ) }; + QgsMessageLog::logMessage( error, QObject::tr( "PostGIS" ) ); + logWrapper.setError( error ); } QgsPostgresResult queryResult; + long long fetchedRows { 0 }; for ( ;; ) { queryResult = mConn->PQgetResult(); @@ -313,6 +320,8 @@ bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature &feature ) int rows = queryResult.PQntuples(); if ( rows == 0 ) continue; + else + fetchedRows += rows; mLastFetch = rows < mFeatureQueueSize; @@ -324,6 +333,11 @@ bool QgsPostgresFeatureIterator::fetchFeature( QgsFeature &feature ) } unlock(); + if ( fetchedRows > 0 ) + { + logWrapper.setFetchedRows( fetchedRows ); + } + #if 0 //disabled dynamic queue size if ( timer.elapsed() > 500 && mFeatureQueueSize > 1 ) { @@ -423,7 +437,7 @@ bool QgsPostgresFeatureIterator::rewind() // move cursor to first record - mConn->PQexecNR( QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) ); + mConn->LoggedPQexecNR( "QgsPostgresFeatureIterator", QStringLiteral( "move absolute 0 in %1" ).arg( mCursorName ) ); mFeatureQueue.clear(); mFetched = 0; mLastFetch = false; diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp index b604432eaac..f8cb811fe51 100644 --- a/src/providers/postgres/qgspostgresprovider.cpp +++ b/src/providers/postgres/qgspostgresprovider.cpp @@ -41,6 +41,7 @@ #include "qgssettings.h" #include "qgsstringutils.h" #include "qgsjsonutils.h" +#include "qgsdbquerylog.h" #include "qgspostgresprovider.h" #include "qgsprovidermetadata.h" @@ -65,13 +66,13 @@ inline qint32 FID2PKINT( qint64 x ) static bool tableExists( QgsPostgresConn &conn, const QString &name ) { - QgsPostgresResult res( conn.PQexec( "SELECT EXISTS ( SELECT oid FROM pg_catalog.pg_class WHERE relname=" + QgsPostgresConn::quotedValue( name ) + ")" ) ); + QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "tableExists" ), "SELECT EXISTS ( SELECT oid FROM pg_catalog.pg_class WHERE relname=" + QgsPostgresConn::quotedValue( name ) + ")" ) ); return res.PQgetvalue( 0, 0 ).startsWith( 't' ); } static bool columnExists( QgsPostgresConn &conn, const QString &table, const QString &column ) { - QgsPostgresResult res( conn.PQexec( "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=" + QgsPostgresConn::quotedValue( table ) + " and column_name=" + QgsPostgresConn::quotedValue( column ) ) ); + QgsPostgresResult res( conn.LoggedPQexec( QStringLiteral( "columnExists" ), "SELECT COUNT(*) FROM information_schema.columns WHERE table_name=" + QgsPostgresConn::quotedValue( table ) + " and column_name=" + QgsPostgresConn::quotedValue( column ) ) ); return res.PQgetvalue( 0, 0 ).toInt() > 0; } @@ -434,7 +435,7 @@ QgsCoordinateReferenceSystem QgsPostgresProvider::sridToCrs( int srid, QgsPostgr { if ( conn ) { - QgsPostgresResult result( conn->PQexec( QStringLiteral( "SELECT auth_name, auth_srid, srtext, proj4text FROM spatial_ref_sys WHERE srid=%1" ).arg( srid ) ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT auth_name, auth_srid, srtext, proj4text FROM spatial_ref_sys WHERE srid=%1" ).arg( srid ) ) ); if ( result.PQresultStatus() == PGRES_TUPLES_OK ) { if ( result.PQntuples() > 0 ) @@ -925,7 +926,7 @@ bool QgsPostgresProvider::loadFields() // Get the table description sql = QStringLiteral( "SELECT description FROM pg_description WHERE objoid=regclass(%1)::oid AND objsubid=0" ).arg( quotedValue( mQuery ) ); - QgsPostgresResult tresult( connectionRO()->PQexec( sql ) ); + QgsPostgresResult tresult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( tresult.PQntuples() > 0 ) { mDataComment = tresult.PQgetvalue( 0, 0 ); @@ -937,7 +938,7 @@ bool QgsPostgresProvider::loadFields() // field name, type, length, and precision (if numeric) sql = QStringLiteral( "SELECT * FROM %1 LIMIT 0" ).arg( mQuery ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); QMap > fmtFieldTypeMap, descrMap, defValMap, identityMap, generatedMap; QMap > attTypeIdMap; @@ -988,7 +989,7 @@ bool QgsPostgresProvider::loadFields() connectionRO()->pgVersion() >= 120000 ? QStringLiteral( ", attgenerated" ) : QString(), tableoidsFilter ); - QgsPostgresResult fmtFieldTypeResult( connectionRO()->PQexec( sql ) ); + QgsPostgresResult fmtFieldTypeResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); for ( int i = 0; i < fmtFieldTypeResult.PQntuples(); ++i ) { Oid attrelid = fmtFieldTypeResult.PQgetvalue( i, 0 ).toUInt(); @@ -1036,7 +1037,7 @@ bool QgsPostgresProvider::loadFields() // Collect type info sql = QStringLiteral( "SELECT oid,typname,typtype,typelem,typlen FROM pg_type %1" ).arg( attroidsFilter ); - QgsPostgresResult typeResult( connectionRO()->PQexec( sql ) ); + QgsPostgresResult typeResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); QMap typeMap; for ( int i = 0; i < typeResult.PQntuples(); ++i ) @@ -1080,7 +1081,7 @@ bool QgsPostgresProvider::loadFields() { // get correct formatted field type for domain sql = QStringLiteral( "SELECT format_type(%1, %2)" ).arg( fldtyp ).arg( fldMod ); - QgsPostgresResult fmtFieldModResult( connectionRO()->PQexec( sql ) ); + QgsPostgresResult fmtFieldModResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( fmtFieldModResult.PQntuples() > 0 ) { formattedFieldType = fmtFieldModResult.PQgetvalue( 0, 0 ); @@ -1413,7 +1414,7 @@ void QgsPostgresProvider::setEditorWidgets() "AND field_name IN ( %4 )" ) . arg( EDITOR_WIDGET_STYLES_TABLE, quotedValue( mSchemaName ), quotedValue( mTableName ), quotedFnames.join( "," ) ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); for ( int i = 0; i < result.PQntuples(); ++i ) { if ( result.PQgetisnull( i, 2 ) ) continue; // config can be null and it's OK @@ -1459,7 +1460,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() { // Check that we can read from the table (i.e., we have select permission). QString sql = QStringLiteral( "SELECT * FROM %1 LIMIT 1" ).arg( mQuery ); - QgsPostgresResult testAccess( connectionRO()->PQexec( sql ) ); + QgsPostgresResult testAccess( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK ) { QgsMessageLog::logMessage( tr( "Unable to access the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ) @@ -1473,7 +1474,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() if ( connectionRO()->pgVersion() >= 90000 ) { - testAccess = connectionRO()->PQexec( QStringLiteral( "SELECT pg_is_in_recovery()" ) ); + testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", QStringLiteral( "SELECT pg_is_in_recovery()" ) ); if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK || testAccess.PQgetvalue( 0, 0 ) == QLatin1String( "t" ) ) { QgsMessageLog::logMessage( tr( "PostgreSQL is still in recovery after a database crash\n(or you are connected to a (read-only) standby server).\nWrite accesses will be denied." ), tr( "PostGIS" ) ); @@ -1517,7 +1518,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() .arg( quotedValue( mQuery ) ); } - testAccess = connectionRO()->PQexec( sql ); + testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK ) { QgsMessageLog::logMessage( tr( "Unable to determine table access privileges for the %1 relation.\nThe error message from the database was:\n%2.\nSQL: %3" ) @@ -1563,7 +1564,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() .arg( quotedValue( mTableName ), quotedValue( mSchemaName ), connectionRO()->pgVersion() < 80100 ? "pg_get_userbyid(relowner)=current_user" : "pg_has_role(relowner,'MEMBER')" ); - testAccess = connectionRO()->PQexec( sql ); + testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( testAccess.PQresultStatus() == PGRES_TUPLES_OK && testAccess.PQntuples() == 1 ) { mEnabledCapabilities |= QgsVectorDataProvider::AddAttributes | QgsVectorDataProvider::DeleteAttributes | QgsVectorDataProvider::RenameAttributes; @@ -1599,7 +1600,7 @@ bool QgsPostgresProvider::hasSufficientPermsAndCapabilities() QString sql = QStringLiteral( "SELECT * FROM %1 LIMIT 1" ).arg( mQuery ); - testAccess = connectionRO()->PQexec( sql ); + testAccess = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( testAccess.PQresultStatus() != PGRES_TUPLES_OK ) { QgsMessageLog::logMessage( tr( "Unable to execute the query.\nThe error message from the database was:\n%1.\nSQL: %2" ) @@ -1652,13 +1653,13 @@ bool QgsPostgresProvider::determinePrimaryKey() { sql = QStringLiteral( "SELECT count(*) FROM pg_inherits WHERE inhparent=%1::regclass" ).arg( quotedValue( mQuery ) ); QgsDebugMsgLevel( QStringLiteral( "Checking whether %1 is a parent table" ).arg( sql ), 2 ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); bool isParentTable( res.PQntuples() == 0 || res.PQgetvalue( 0, 0 ).toInt() > 0 ); sql = QStringLiteral( "SELECT indexrelid FROM pg_index WHERE indrelid=%1::regclass AND (indisprimary OR indisunique) ORDER BY CASE WHEN indisprimary THEN 1 ELSE 2 END LIMIT 1" ).arg( quotedValue( mQuery ) ); QgsDebugMsgLevel( QStringLiteral( "Retrieving first primary or unique index: %1" ).arg( sql ), 2 ); - res = connectionRO()->PQexec( sql ); + res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Got %1 rows." ).arg( res.PQntuples() ), 2 ); QStringList log; @@ -1686,7 +1687,7 @@ bool QgsPostgresProvider::determinePrimaryKey() { // If there is an generated id on the table, use that instead, sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attidentity IN ('a','d') AND attrelid=regclass(%1) LIMIT 1" ).arg( quotedValue( mQuery ) ); - res = connectionRO()->PQexec( sql ); + res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( res.PQntuples() == 1 ) { // Could warn the user here that performance will suffer if @@ -1703,7 +1704,7 @@ bool QgsPostgresProvider::determinePrimaryKey() // If there is an oid on the table, use that instead, sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='oid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) ); - res = connectionRO()->PQexec( sql ); + res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( res.PQntuples() == 1 ) { // Could warn the user here that performance will suffer if @@ -1717,7 +1718,7 @@ bool QgsPostgresProvider::determinePrimaryKey() { sql = QStringLiteral( "SELECT attname FROM pg_attribute WHERE attname='ctid' AND attrelid=regclass(%1)" ).arg( quotedValue( mQuery ) ); - res = connectionRO()->PQexec( sql ); + res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( res.PQntuples() == 1 ) { mPrimaryKeyType = PktTid; @@ -1750,7 +1751,7 @@ bool QgsPostgresProvider::determinePrimaryKey() sql = QStringLiteral( "SELECT attname,attnotnull FROM pg_index,pg_attribute WHERE indexrelid=%1 AND indrelid=attrelid AND pg_attribute.attnum=any(pg_index.indkey)" ).arg( indrelid ); QgsDebugMsgLevel( "Retrieving key columns: " + sql, 2 ); - res = connectionRO()->PQexec( sql ); + res = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Got %1 rows." ).arg( res.PQntuples() ), 2 ); bool mightBeNull = false; @@ -1934,7 +1935,7 @@ bool QgsPostgresProvider::uniqueData( const QString "edColNames ) mQuery, filterWhereClause() ); - QgsPostgresResult unique( connectionRO()->PQexec( sql ) ); + QgsPostgresResult unique( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( unique.PQresultStatus() != PGRES_TUPLES_OK ) { @@ -1962,7 +1963,7 @@ QVariant QgsPostgresProvider::minimumValue( int index ) const sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql ); - QgsPostgresResult rmin( connectionRO()->PQexec( sql ) ); + QgsPostgresResult rmin( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); return convertValue( fld.type(), fld.subType(), rmin.PQgetvalue( 0, 0 ), fld.typeName() ); } catch ( PGFieldNotFound ) @@ -1998,7 +1999,7 @@ QSet QgsPostgresProvider::uniqueValues( int index, int limit ) const sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) { for ( int i = 0; i < res.PQntuples(); i++ ) @@ -2040,7 +2041,7 @@ QStringList QgsPostgresProvider::uniqueStringsMatching( int index, const QString sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), sql ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) { for ( int i = 0; i < res.PQntuples(); i++ ) @@ -2081,7 +2082,7 @@ void QgsPostgresProvider::enumValues( int index, QStringList &enumList ) const //is type an enum? const QString typeSql = QStringLiteral( "SELECT typtype FROM pg_type WHERE typname=%1" ).arg( quotedValue( typeName ) ); - QgsPostgresResult typeRes( connectionRO()->PQexec( typeSql ) ); + QgsPostgresResult typeRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), typeSql ) ); if ( typeRes.PQresultStatus() != PGRES_TUPLES_OK || typeRes.PQntuples() < 1 ) { mShared->setFieldSupportsEnumValues( index, false ); @@ -2114,7 +2115,7 @@ bool QgsPostgresProvider::parseEnumRange( QStringList &enumValues, const QString QString enumRangeSql = QStringLiteral( "SELECT enumlabel FROM pg_catalog.pg_enum WHERE enumtypid=(SELECT atttypid::regclass FROM pg_attribute WHERE attrelid=%1::regclass AND attname=%2)" ) .arg( quotedValue( mQuery ), quotedValue( attributeName ) ); - QgsPostgresResult enumRangeRes( connectionRO()->PQexec( enumRangeSql ) ); + QgsPostgresResult enumRangeRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), enumRangeSql ) ); if ( enumRangeRes.PQresultStatus() != PGRES_TUPLES_OK ) return false; @@ -2132,7 +2133,7 @@ bool QgsPostgresProvider::parseDomainCheckConstraint( QStringList &enumValues, c //is it a domain type with a check constraint? QString domainSql = QStringLiteral( "SELECT domain_name, domain_schema FROM information_schema.columns WHERE table_name=%1 AND column_name=%2" ).arg( quotedValue( mTableName ), quotedValue( attributeName ) ); - QgsPostgresResult domainResult( connectionRO()->PQexec( domainSql ) ); + QgsPostgresResult domainResult( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), domainSql ) ); if ( domainResult.PQresultStatus() == PGRES_TUPLES_OK && domainResult.PQntuples() > 0 && !domainResult.PQgetvalue( 0, 0 ).isNull() ) { QString domainCheckDefinitionSql; @@ -2168,7 +2169,7 @@ bool QgsPostgresProvider::parseDomainCheckConstraint( QStringList &enumValues, c } - QgsPostgresResult domainCheckRes( connectionRO()->PQexec( domainCheckDefinitionSql ) ); + QgsPostgresResult domainCheckRes( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), domainCheckDefinitionSql ) ); if ( domainCheckRes.PQresultStatus() == PGRES_TUPLES_OK && domainCheckRes.PQntuples() > 0 ) { QString checkDefinition = domainCheckRes.PQgetvalue( 0, 0 ); @@ -2230,7 +2231,7 @@ QVariant QgsPostgresProvider::maximumValue( int index ) const sql = QStringLiteral( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql ); - QgsPostgresResult rmax( connectionRO()->PQexec( sql ) ); + QgsPostgresResult rmax( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); return convertValue( fld.type(), fld.subType(), rmax.PQgetvalue( 0, 0 ), fld.typeName() ); } @@ -2278,7 +2279,7 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const { QgsField fld = field( fieldId ); - QgsPostgresResult res( connectionRO()->PQexec( QStringLiteral( "SELECT %1" ).arg( defVal ) ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT %1" ).arg( defVal ) ) ); if ( res.result() ) { @@ -2315,7 +2316,7 @@ QString QgsPostgresProvider::paramValue( const QString &fieldValue, const QStrin if ( fieldValue == defaultValue && !defaultValue.isNull() ) { - QgsPostgresResult result( connectionRO()->PQexec( QStringLiteral( "SELECT %1" ).arg( defaultValue ) ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT %1" ).arg( defaultValue ) ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); @@ -2336,7 +2337,7 @@ bool QgsPostgresProvider::getTopoLayerInfo() .arg( quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( mGeometryColumn ) ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { throw PGException( result ); // we should probably not do this @@ -2369,7 +2370,7 @@ void QgsPostgresProvider::dropOrphanedTopoGeoms() QgsDebugMsgLevel( "TopoGeom orphans cleanup query: " + sql, 2 ); - connectionRW()->PQexecNR( sql ); + connectionRW()->LoggedPQexecNR( "QgsPostgresProvider", sql ); } QString QgsPostgresProvider::geomParam( int offset ) const @@ -2748,7 +2749,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) } } - conn->PQexecNR( QStringLiteral( "DEALLOCATE addfeatures" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE addfeatures" ) ); returnvalue &= conn->commit(); if ( mTransaction ) @@ -2760,7 +2761,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist, Flags flags ) { pushError( tr( "PostGIS error while adding features: %1" ).arg( e.errorMessage() ) ); conn->rollback(); - conn->PQexecNR( QStringLiteral( "DEALLOCATE addfeatures" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE addfeatures" ) ); returnvalue = false; } @@ -2808,7 +2809,7 @@ bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds &ids ) QgsDebugMsgLevel( "delete sql: " + sql, 2 ); //send DELETE statement and do error handling - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); @@ -2872,7 +2873,7 @@ bool QgsPostgresProvider::truncate() QgsDebugMsgLevel( "truncate sql: " + sql, 2 ); //send truncate statement and do error handling - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); @@ -2947,7 +2948,7 @@ bool QgsPostgresProvider::addAttributes( const QList &attributes ) } //send sql statement and do error handling - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) throw PGException( result ); @@ -2959,7 +2960,7 @@ bool QgsPostgresProvider::addAttributes( const QList &attributes ) .arg( mQuery, quotedIdentifier( iter->name() ), quotedValue( iter->comment() ) ); - result = conn->PQexec( sql ); + result = conn->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) throw PGException( result ); } @@ -3014,7 +3015,7 @@ bool QgsPostgresProvider::deleteAttributes( const QgsAttributeIds &ids ) quotedIdentifier( column ) ); //send sql statement and do error handling - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) throw PGException( result ); @@ -3081,7 +3082,7 @@ bool QgsPostgresProvider::renameAttributes( const QgsFieldNameMap &renamedAttrib { conn->begin(); //send sql statement and do error handling - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) throw PGException( result ); returnvalue = conn->commit(); @@ -3208,7 +3209,7 @@ bool QgsPostgresProvider::changeAttributeValues( const QgsChangedAttributesMap & // or if the user only changed GENERATED fields in the form/attribute table. if ( numChangedFields > 0 ) { - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); } @@ -3408,7 +3409,7 @@ bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_m .arg( quotedIdentifier( mTopoLayerInfo.topologyName ) ) .arg( mTopoLayerInfo.layerId ) .arg( old_tg_id ); - result = conn->PQexec( replace ); + result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), replace ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { QgsDebugMsg( QStringLiteral( "Exception thrown due to PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" ) @@ -3423,7 +3424,7 @@ bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_m .arg( mTopoLayerInfo.layerId ) .arg( new_tg_id ); QgsDebugMsgLevel( "relation swap: " + replace, 2 ); - result = conn->PQexec( replace ); + result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), replace ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { QgsDebugMsg( QStringLiteral( "Exception thrown due to PQexec of this query returning != PGRES_COMMAND_OK (%1 != expected %2): %3" ) @@ -3434,11 +3435,11 @@ bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_m } // for each feature - conn->PQexecNR( QStringLiteral( "DEALLOCATE updatefeatures" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeatures" ) ); if ( mSpatialColType == SctTopoGeometry ) { - connectionRO()->PQexecNR( QStringLiteral( "DEALLOCATE getid" ) ); - conn->PQexecNR( QStringLiteral( "DEALLOCATE replacetopogeom" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE getid" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE replacetopogeom" ) ); } returnvalue &= conn->commit(); @@ -3449,11 +3450,11 @@ bool QgsPostgresProvider::changeGeometryValues( const QgsGeometryMap &geometry_m { pushError( tr( "PostGIS error while changing geometry values: %1" ).arg( e.errorMessage() ) ); conn->rollback(); - conn->PQexecNR( QStringLiteral( "DEALLOCATE updatefeatures" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeatures" ) ); if ( mSpatialColType == SctTopoGeometry ) { - connectionRO()->PQexecNR( QStringLiteral( "DEALLOCATE getid" ) ); - conn->PQexecNR( QStringLiteral( "DEALLOCATE replacetopogeom" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE getid" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE replacetopogeom" ) ); } returnvalue = false; } @@ -3575,7 +3576,7 @@ bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_ma { sql += QStringLiteral( " WHERE %1" ).arg( whereClause( fid ) ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK && result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); } @@ -3609,7 +3610,7 @@ bool QgsPostgresProvider::changeFeatures( const QgsChangedAttributesMap &attr_ma throw PGException( result ); } - conn->PQexecNR( QStringLiteral( "DEALLOCATE updatefeature" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "DEALLOCATE updatefeature" ) ); } // update feature id map if key was changed @@ -3694,7 +3695,7 @@ bool QgsPostgresProvider::setSubsetString( const QString &theSQL, bool updateFea sql += QLatin1String( " LIMIT 0" ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( res.PQresultStatus() != PGRES_TUPLES_OK ) { pushError( res.PQresultErrorMessage() ); @@ -3759,7 +3760,7 @@ long long QgsPostgresProvider::featureCount() const // parse explain output to estimate feature count // we don't use pg_class reltuples because it returns 0 for view sql = QStringLiteral( "EXPLAIN (FORMAT JSON) SELECT 1 FROM %1%2" ).arg( mQuery, filterWhereClause() ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); const QString json = result.PQgetvalue( 0, 0 ); const QVariantList explain = QgsJsonUtils::parseJson( json ).toList(); @@ -3774,14 +3775,14 @@ long long QgsPostgresProvider::featureCount() const else { sql = QStringLiteral( "SELECT reltuples::bigint FROM pg_catalog.pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); num = result.PQgetvalue( 0, 0 ).toLongLong(); } } else { sql = QStringLiteral( "SELECT count(*) FROM %1%2" ).arg( mQuery, filterWhereClause() ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); QgsDebugMsgLevel( "number of features as text: " + result.PQgetvalue( 0, 0 ), 2 ); @@ -3798,7 +3799,7 @@ long long QgsPostgresProvider::featureCount() const bool QgsPostgresProvider::empty() const { QString sql = QStringLiteral( "SELECT EXISTS (SELECT * FROM %1%2 LIMIT 1)" ).arg( mQuery, filterWhereClause() ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( res.PQresultStatus() != PGRES_TUPLES_OK ) { pushError( res.PQresultErrorMessage() ); @@ -3830,13 +3831,13 @@ QgsRectangle QgsPostgresProvider::extent() const .arg( quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( mGeometryColumn ) ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() == PGRES_TUPLES_OK && result.PQntuples() == 1 ) { if ( result.PQgetvalue( 0, 0 ).toInt() > 0 ) { sql = QStringLiteral( "SELECT reltuples::bigint FROM pg_catalog.pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() == PGRES_TUPLES_OK && result.PQntuples() == 1 && result.PQgetvalue( 0, 0 ).toLong() > 0 ) @@ -3847,7 +3848,7 @@ QgsRectangle QgsPostgresProvider::extent() const quotedValue( mSchemaName ), quotedValue( mTableName ), quotedValue( mGeometryColumn ) ); - result = mConnectionRO->PQexec( sql ); + result = mConnectionRO->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() == PGRES_TUPLES_OK && result.PQntuples() == 1 && !result.PQgetisnull( 0, 0 ) ) { ext = result.PQgetvalue( 0, 0 ); @@ -3884,9 +3885,9 @@ QgsRectangle QgsPostgresProvider::extent() const mQuery, filterWhereClause() ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) - connectionRO()->PQexecNR( QStringLiteral( "ROLLBACK" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "ROLLBACK" ) ); else if ( result.PQntuples() == 1 && !result.PQgetisnull( 0, 0 ) ) ext = result.PQgetvalue( 0, 0 ); } @@ -3960,7 +3961,7 @@ bool QgsPostgresProvider::getGeometryDetails() } QgsDebugMsgLevel( QStringLiteral( "Getting the spatial column type: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( PGRES_TUPLES_OK == result.PQresultStatus() ) { geomColType = result.PQgetvalue( 0, 0 ); @@ -3996,17 +3997,17 @@ bool QgsPostgresProvider::getGeometryDetails() QgsDebugMsgLevel( QStringLiteral( "Getting geometry column: %1" ).arg( sql ), 2 ); - QgsPostgresResult result( connectionRO()->PQexec( sql ) ); + QgsPostgresResult result( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( PGRES_TUPLES_OK == result.PQresultStatus() ) { Oid tableoid = result.PQftable( 0 ); int column = result.PQftablecol( 0 ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( tableoid > 0 && PGRES_TUPLES_OK == result.PQresultStatus() ) { sql = QStringLiteral( "SELECT pg_namespace.nspname,pg_class.relname FROM pg_class,pg_namespace WHERE pg_class.relnamespace=pg_namespace.oid AND pg_class.oid=%1" ).arg( tableoid ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( PGRES_TUPLES_OK == result.PQresultStatus() && 1 == result.PQntuples() ) { @@ -4014,7 +4015,7 @@ bool QgsPostgresProvider::getGeometryDetails() tableName = result.PQgetvalue( 0, 1 ); sql = QStringLiteral( "SELECT a.attname, t.typname FROM pg_attribute a, pg_type t WHERE a.attrelid=%1 AND a.attnum=%2 AND a.atttypid = t.oid" ).arg( tableoid ).arg( column ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( PGRES_TUPLES_OK == result.PQresultStatus() && 1 == result.PQntuples() ) { geomCol = result.PQgetvalue( 0, 0 ); @@ -4061,7 +4062,7 @@ bool QgsPostgresProvider::getGeometryDetails() quotedValue( schemaName ) ); QgsDebugMsgLevel( QStringLiteral( "Getting geometry column: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Geometry column query returned %1 rows" ).arg( result.PQntuples() ), 2 ); if ( result.PQntuples() == 1 ) @@ -4086,7 +4087,7 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } if ( detectedType.isEmpty() ) @@ -4098,7 +4099,7 @@ bool QgsPostgresProvider::getGeometryDetails() quotedValue( schemaName ) ); QgsDebugMsgLevel( QStringLiteral( "Getting geography column: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql, false ); + result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Geography column query returned %1" ).arg( result.PQntuples() ), 2 ); if ( result.PQntuples() == 1 ) @@ -4111,7 +4112,7 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } } @@ -4131,7 +4132,7 @@ bool QgsPostgresProvider::getGeometryDetails() quotedValue( schemaName ) ); QgsDebugMsgLevel( QStringLiteral( "Getting TopoGeometry column: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql, false ); + result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "TopoGeometry column query returned %1" ).arg( result.PQntuples() ), 2 ); if ( result.PQntuples() == 1 ) @@ -4142,7 +4143,7 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } } @@ -4155,7 +4156,7 @@ bool QgsPostgresProvider::getGeometryDetails() quotedValue( schemaName ) ); QgsDebugMsgLevel( QStringLiteral( "Getting pointcloud column: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql, false ); + result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Pointcloud column query returned %1" ).arg( result.PQntuples() ), 2 ); if ( result.PQntuples() == 1 ) @@ -4166,7 +4167,7 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } } @@ -4181,7 +4182,7 @@ bool QgsPostgresProvider::getGeometryDetails() quotedValue( geomCol ), quotedValue( schemaName ) ); QgsDebugMsgLevel( QStringLiteral( "Getting column datatype: %1" ).arg( sql ), 2 ); - result = connectionRO()->PQexec( sql, false ); + result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql ); QgsDebugMsgLevel( QStringLiteral( "Column datatype query returned %1" ).arg( result.PQntuples() ), 2 ); if ( result.PQntuples() == 1 ) { @@ -4197,19 +4198,19 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } } } else { sql = QStringLiteral( "SELECT %1 FROM %2 LIMIT 0" ).arg( quotedIdentifier( mGeometryColumn ), mQuery ); - result = connectionRO()->PQexec( sql ); + result = connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ); if ( PGRES_TUPLES_OK == result.PQresultStatus() ) { sql = QStringLiteral( "SELECT (SELECT t.typname FROM pg_type t WHERE oid = %1), upper(postgis_typmod_type(%2)), postgis_typmod_srid(%2)" ) .arg( QString::number( result.PQftype( 0 ) ), QString::number( result.PQfmod( 0 ) ) ); - result = connectionRO()->PQexec( sql, false ); + result = connectionRO()->LoggedPQexecNoLogError( "QgsPostgresProvider", sql ); if ( result.PQntuples() == 1 ) { geomColType = result.PQgetvalue( 0, 0 ); @@ -4231,7 +4232,7 @@ bool QgsPostgresProvider::getGeometryDetails() } else { - connectionRO()->PQexecNR( QStringLiteral( "COMMIT" ) ); + connectionRO()->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); detectedType = mRequestedGeomType == QgsWkbTypes::Unknown ? QString() : QgsPostgresConn::postgisWkbTypeName( mRequestedGeomType ); } } @@ -4587,13 +4588,13 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u try { - conn->PQexecNR( QStringLiteral( "BEGIN" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "BEGIN" ) ); // We want a valid schema name ... if ( schemaName.isEmpty() ) { QString sql = QString( "SELECT current_schema" ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); schemaName = result.PQgetvalue( 0, 0 ); @@ -4610,7 +4611,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u .arg( quotedValue( tableName ), quotedValue( schemaName ) ); - QgsPostgresResult result( conn->PQexec( sql ) ); + QgsPostgresResult result( conn->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); @@ -4626,7 +4627,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u .arg( quotedValue( schemaName ), quotedValue( tableName ) ); - result = conn->PQexec( sql ); + result = conn->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); } @@ -4658,7 +4659,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u } sql += QStringLiteral( ", PRIMARY KEY (%1) )" ) .arg( pk ); - result = conn->PQexec( sql ); + result = conn->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) throw PGException( result ); @@ -4679,7 +4680,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u .arg( quotedValue( geometryType ) ) .arg( dim ); - result = conn->PQexec( sql ); + result = conn->LoggedPQexec( "QgsPostgresProvider", sql ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) throw PGException( result ); } @@ -4688,7 +4689,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u geometryColumn.clear(); } - conn->PQexecNR( QStringLiteral( "COMMIT" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "COMMIT" ) ); } catch ( PGException &e ) { @@ -4697,7 +4698,7 @@ Qgis::VectorExportResult QgsPostgresProvider::createEmptyLayer( const QString &u .arg( schemaTableName, e.errorMessage() ); - conn->PQexecNR( QStringLiteral( "ROLLBACK" ) ); + conn->LoggedPQexecNR( "QgsPostgresProvider", QStringLiteral( "ROLLBACK" ) ); conn->unref(); return Qgis::VectorExportResult::ErrorCreatingLayer; } @@ -4843,7 +4844,7 @@ QString QgsPostgresProvider::description() const QgsPostgresResult result; /* TODO: expose a cached QgsPostgresConn::version() ? */ - result = lConnectionRO->PQexec( QStringLiteral( "SELECT version()" ) ); + result = lConnectionRO->LoggedPQexec( QStringLiteral( "QgsPostgresProvider" ), QStringLiteral( "SELECT version()" ) ); if ( result.PQresultStatus() == PGRES_TUPLES_OK ) { pgVersion = result.PQgetvalue( 0, 0 ); @@ -4879,7 +4880,7 @@ QString QgsPostgresProvider::getNextString( const QString &txt, int &i, const QS } i += match.captured( 1 ).length() + 2; jumpSpace( txt, i ); - if ( !QStringView{txt}.mid( i ).startsWith( sep ) && i < txt.length() ) + if ( !QStringView{txt} .mid( i ).startsWith( sep ) && i < txt.length() ) { QgsMessageLog::logMessage( tr( "Cannot find separator: %1" ).arg( txt.mid( i ) ), tr( "PostGIS" ) ); return QString(); @@ -4892,9 +4893,9 @@ QString QgsPostgresProvider::getNextString( const QString &txt, int &i, const QS int start = i; for ( ; i < txt.length(); i++ ) { - if ( QStringView{txt}.mid( i ).startsWith( sep ) ) + if ( QStringView{txt} .mid( i ).startsWith( sep ) ) { - QStringView v( QStringView{txt}.mid( start, i - start ) ); + QStringView v( QStringView{txt} .mid( start, i - start ) ); i += sep.length(); return v.trimmed().toString(); } @@ -5130,7 +5131,7 @@ QList QgsPostgresProvider::discoverRelations( const QgsVectorLayer " fk.conname ;" ); - QgsPostgresResult sqlResult( connectionRO()->PQexec( sql ) ); + QgsPostgresResult sqlResult( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); if ( sqlResult.PQresultStatus() != PGRES_TUPLES_OK ) { QgsLogger::warning( "Error getting the foreign keys of " + mTableName ); @@ -5224,7 +5225,7 @@ QgsPostgresProvider::Relkind QgsPostgresProvider::relkind() const else { QString sql = QStringLiteral( "SELECT relkind FROM pg_class WHERE oid=regclass(%1)::oid" ).arg( quotedValue( mQuery ) ); - QgsPostgresResult res( connectionRO()->PQexec( sql ) ); + QgsPostgresResult res( connectionRO()->LoggedPQexec( "QgsPostgresProvider", sql ) ); QString type = res.PQgetvalue( 0, 0 ); mKind = Relkind::Unknown; @@ -5356,7 +5357,7 @@ bool QgsPostgresProviderMetadata::styleExists( const QString &uri, const QString .arg( wkbTypeString ) .arg( QgsPostgresConn::quotedValue( styleId.isEmpty() ? dsUri.table() : styleId ) ); - QgsPostgresResult res( conn->PQexec( checkQuery ) ); + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), checkQuery ) ); if ( res.PQresultStatus() == PGRES_TUPLES_OK ) { return res.PQntuples() > 0; @@ -5389,22 +5390,23 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & if ( !tableExists( *conn, QStringLiteral( "layer_styles" ) ) ) { - QgsPostgresResult res( conn->PQexec( "CREATE TABLE layer_styles(" - "id SERIAL PRIMARY KEY" - ",f_table_catalog varchar" - ",f_table_schema varchar" - ",f_table_name varchar" - ",f_geometry_column varchar" - ",styleName text" - ",styleQML xml" - ",styleSLD xml" - ",useAsDefault boolean" - ",description text" - ",owner varchar(63) DEFAULT CURRENT_USER" - ",ui xml" - ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" - ",type varchar" - ")" ) ); + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), + "CREATE TABLE layer_styles(" + "id SERIAL PRIMARY KEY" + ",f_table_catalog varchar" + ",f_table_schema varchar" + ",f_table_name varchar" + ",f_geometry_column varchar" + ",styleName text" + ",styleQML xml" + ",styleSLD xml" + ",useAsDefault boolean" + ",description text" + ",owner varchar(63) DEFAULT CURRENT_USER" + ",ui xml" + ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" + ",type varchar" + ")" ) ); if ( res.PQresultStatus() != PGRES_COMMAND_OK ) { errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() ); @@ -5416,7 +5418,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & { if ( !columnExists( *conn, QStringLiteral( "layer_styles" ), QStringLiteral( "type" ) ) ) { - QgsPostgresResult res( conn->PQexec( "ALTER TABLE layer_styles ADD COLUMN type varchar NULL" ) ); + QgsPostgresResult res( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), "ALTER TABLE layer_styles ADD COLUMN type varchar NULL" ) ); if ( res.PQresultStatus() != PGRES_COMMAND_OK ) { errCause = QObject::tr( "Unable to add column type to layer_styles table. Maybe this is due to table permissions (user=%1). Please contact your database admin" ).arg( dsUri.username() ); @@ -5481,7 +5483,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & .arg( wkbTypeString ) .arg( QgsPostgresConn::quotedValue( styleName.isEmpty() ? dsUri.table() : styleName ) ); - QgsPostgresResult res( conn->PQexec( checkQuery ) ); + QgsPostgresResult res( conn->LoggedPQexec( "QgsPostgresProviderMetadata", checkQuery ) ); if ( res.PQntuples() > 0 ) { sql = QString( "UPDATE layer_styles" @@ -5529,7 +5531,7 @@ bool QgsPostgresProviderMetadata::saveStyle( const QString &uri, const QString & sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql ); } - res = conn->PQexec( sql ); + res = conn->LoggedPQexec( "QgsPostgresProviderMetadata", sql ); bool saved = res.PQresultStatus() == PGRES_COMMAND_OK; if ( !saved ) @@ -5610,7 +5612,7 @@ QString QgsPostgresProviderMetadata::loadStyle( const QString &uri, QString &err .arg( wkbTypeString ); } - QgsPostgresResult result( conn->PQexec( selectQmlQuery ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectQmlQuery ) ); QString style = result.PQntuples() == 1 ? result.PQgetvalue( 0, 0 ) : QString(); conn->unref(); @@ -5660,7 +5662,7 @@ int QgsPostgresProviderMetadata::listStyles( const QString &uri, QStringList &id QString( "f_geometry_column=%1" ).arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) ) .arg( wkbTypeString ); - QgsPostgresResult result( conn->PQexec( selectRelatedQuery ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectRelatedQuery ) ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectRelatedQuery ) ); @@ -5687,7 +5689,7 @@ int QgsPostgresProviderMetadata::listStyles( const QString &uri, QStringList &id .arg( QgsPostgresConn::quotedValue( dsUri.geometryColumn() ) ) .arg( wkbTypeString ); - result = conn->PQexec( selectOthersQuery ); + result = conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectOthersQuery ); if ( result.PQresultStatus() != PGRES_TUPLES_OK ) { QgsMessageLog::logMessage( QObject::tr( "Error executing query: %1" ).arg( selectOthersQuery ) ); @@ -5723,7 +5725,7 @@ bool QgsPostgresProviderMetadata::deleteStyleById( const QString &uri, const QSt { QString deleteStyleQuery = QStringLiteral( "DELETE FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) ); - QgsPostgresResult result( conn->PQexec( deleteStyleQuery ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), deleteStyleQuery ) ); if ( result.PQresultStatus() != PGRES_COMMAND_OK ) { QgsDebugMsg( @@ -5755,7 +5757,7 @@ QString QgsPostgresProviderMetadata::getStyleById( const QString &uri, const QSt QString style; QString selectQmlQuery = QStringLiteral( "SELECT styleQml FROM layer_styles WHERE id=%1" ).arg( QgsPostgresConn::quotedValue( styleId ) ); - QgsPostgresResult result( conn->PQexec( selectQmlQuery ) ); + QgsPostgresResult result( conn->LoggedPQexec( QStringLiteral( "QgsPostgresProviderMetadata" ), selectQmlQuery ) ); if ( result.PQresultStatus() == PGRES_TUPLES_OK ) { if ( result.PQntuples() == 1 ) diff --git a/src/providers/postgres/qgspostgresproviderconnection.cpp b/src/providers/postgres/qgspostgresproviderconnection.cpp index 42f8a00530e..325305c680e 100644 --- a/src/providers/postgres/qgspostgresproviderconnection.cpp +++ b/src/providers/postgres/qgspostgresproviderconnection.cpp @@ -257,7 +257,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsPostgresProviderConnection } std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); - std::unique_ptr res = std::make_unique( conn->PQexec( sql ) ); + std::unique_ptr res = std::make_unique( conn->LoggedPQexec( "QgsPostgresProviderConnection", sql ) ); std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); results.setQueryExecutionTime( std::chrono::duration_cast( end - begin ).count() ); diff --git a/src/providers/postgres/qgspostgrestransaction.cpp b/src/providers/postgres/qgspostgrestransaction.cpp index 55caa27bf2b..615606a51f0 100644 --- a/src/providers/postgres/qgspostgrestransaction.cpp +++ b/src/providers/postgres/qgspostgrestransaction.cpp @@ -72,7 +72,7 @@ bool QgsPostgresTransaction::executeSql( const QString &sql, QString &errorMsg, } QgsDebugMsg( QStringLiteral( "Transaction sql: %1" ).arg( sql ) ); - QgsPostgresResult r( mConn->PQexec( sql, true ) ); + QgsPostgresResult r( mConn->LoggedPQexec( "QgsPostgresTransaction", sql ) ); if ( r.PQresultStatus() == PGRES_BAD_RESPONSE || r.PQresultStatus() == PGRES_FATAL_ERROR ) { diff --git a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp index adc4f8304d8..2c277b770d0 100644 --- a/src/providers/spatialite/qgsspatialitefeatureiterator.cpp +++ b/src/providers/spatialite/qgsspatialitefeatureiterator.cpp @@ -235,6 +235,10 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature sqliteStatement = nullptr; close(); } + else + { + mQueryLogWrapper = std::make_unique( mLastSql, mSource->mSqlitePath, QStringLiteral( "spatialite" ), QStringLiteral( "QgsSpatiaLiteFeatureIterator" ), QGS_QUERY_LOG_ORIGIN ); + } } } @@ -395,6 +399,7 @@ bool QgsSpatiaLiteFeatureIterator::prepareStatement( const QString &whereClause, QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( mSqliteHandle ) ), QObject::tr( "SpatiaLite" ) ); return false; } + mLastSql = sql; } catch ( QgsSpatiaLiteProvider::SLFieldNotFound ) { diff --git a/src/providers/spatialite/qgsspatialitefeatureiterator.h b/src/providers/spatialite/qgsspatialitefeatureiterator.h index a06d93cc86a..ac49e0206a5 100644 --- a/src/providers/spatialite/qgsspatialitefeatureiterator.h +++ b/src/providers/spatialite/qgsspatialitefeatureiterator.h @@ -18,6 +18,7 @@ #include "qgsfeatureiterator.h" #include "qgsfields.h" #include "qgscoordinatetransform.h" +#include "qgsdbquerylog.h" extern "C" { @@ -113,6 +114,11 @@ class QgsSpatiaLiteFeatureIterator final: public QgsAbstractFeatureIteratorFromS QgsCoordinateTransform mTransform; QgsGeometry mDistanceWithinGeom; std::unique_ptr< QgsGeometryEngine > mDistanceWithinEngine; + + // Last prepared sql statement for logging purposes + QString mLastSql; + + std::unique_ptr mQueryLogWrapper; }; #endif // QGSSPATIALITEFEATUREITERATOR_H diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp index 665ec264358..da7f8573d1e 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.cpp +++ b/src/providers/spatialite/qgsspatialiteprovider.cpp @@ -33,6 +33,7 @@ email : a.furieri@lqt.it #include "qgsspatialiteconnection.h" #include "qgsspatialitetransaction.h" #include "qgsspatialiteproviderconnection.h" +#include "qgsdbquerylog.h" #include "qgsjsonutils.h" #include "qgsvectorlayer.h" @@ -225,7 +226,7 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString try { - int ret = sqlite3_exec( sqliteHandle, "BEGIN", nullptr, nullptr, &errMsg ); + int ret = exec_sql( sqliteHandle, "BEGIN", uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); @@ -237,14 +238,14 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString sql = QStringLiteral( "DROP TABLE IF EXISTS %1" ) .arg( QgsSqliteUtils::quotedIdentifier( tableName ) ); - ret = sqlite3_exec( sqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = exec_sql( sqliteHandle, sql.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); sql = QStringLiteral( "DELETE FROM geometry_columns WHERE upper(f_table_name) = upper(%1)" ) .arg( QgsSqliteUtils::quotedString( tableName ) ); - ret = sqlite3_exec( sqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = exec_sql( sqliteHandle, sql.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); } @@ -255,7 +256,7 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString primaryKeyType, primaryKeyType == QLatin1String( "INTEGER" ) ? QStringLiteral( " AUTOINCREMENT" ) : QString() ); - ret = sqlite3_exec( sqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = exec_sql( sqliteHandle, sql.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); @@ -333,7 +334,7 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString .arg( QgsSqliteUtils::quotedString( geometryType ) ) .arg( dim ); - ret = sqlite3_exec( sqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = exec_sql( sqliteHandle, sql.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); } @@ -342,7 +343,7 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString geometryColumn = QString(); } - ret = sqlite3_exec( sqliteHandle, "COMMIT", nullptr, nullptr, &errMsg ); + ret = exec_sql( sqliteHandle, "COMMIT", uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) throw SLException( errMsg ); @@ -363,7 +364,7 @@ Qgis::VectorExportResult QgsSpatiaLiteProvider::createEmptyLayer( const QString if ( toCommit ) { // ROLLBACK after some previous error - sqlite3_exec( sqliteHandle, "ROLLBACK", nullptr, nullptr, nullptr ); + exec_sql( sqliteHandle, "ROLLBACK", uri, nullptr, QGS_QUERY_LOG_ORIGIN ); } QgsSqliteHandle::closeDb( handle ); @@ -486,7 +487,7 @@ QgsSpatiaLiteProvider::QgsSpatiaLiteProvider( QString const &uri, const Provider for ( const auto &pragma : pragmaList ) { char *errMsg = nullptr; - int ret = exec_sql( QStringLiteral( "PRAGMA %1" ).arg( pragma ), errMsg ); + int ret = exec_sql( mSqliteHandle, QStringLiteral( "PRAGMA %1" ).arg( pragma ), uri, errMsg ); if ( ret != SQLITE_OK ) { QgsDebugMsg( QStringLiteral( "PRAGMA " ) + pragma + QString( " failed : %1" ).arg( errMsg ? errMsg : "" ) ); @@ -1143,14 +1144,15 @@ void QgsSpatiaLiteProvider::handleError( const QString &sql, char *errorMessage, if ( ! savepointId.isEmpty() ) { // ROLLBACK after some previous error - ( void )exec_sql( QStringLiteral( "ROLLBACK TRANSACTION TO \"%1\"" ).arg( savepointId ) ); + ( void )exec_sql( sqliteHandle(), QStringLiteral( "ROLLBACK TRANSACTION TO \"%1\"" ).arg( savepointId ), uri().uri(), nullptr, QGS_QUERY_LOG_ORIGIN ); } } -int QgsSpatiaLiteProvider::exec_sql( const QString &sql, char *errMsg ) +int QgsSpatiaLiteProvider::exec_sql( sqlite3 *handle, const QString &sql, const QString &uri, char *errMsg, const QString &origin ) { + QgsDatabaseQueryLogWrapper logWrapper( sql, uri, QStringLiteral( "spatialite" ), QStringLiteral( "QgsSpatiaLiteProvider" ), origin ); // Use transaction's handle (if any) - return sqlite3_exec( sqliteHandle(), sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + return sqlite3_exec( handle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); } sqlite3 *QgsSpatiaLiteProvider::sqliteHandle() const @@ -1391,7 +1393,7 @@ bool QgsSpatiaLiteProvider::hasRowid() // table without rowid column QString sql = QStringLiteral( "SELECT rowid FROM %1 WHERE 0" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) ); char *errMsg = nullptr; - return exec_sql( sql, errMsg ) == SQLITE_OK; + return exec_sql( sqliteHandle( ), sql, uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ) == SQLITE_OK; } @@ -4154,7 +4156,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle(), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret == SQLITE_OK ) { toCommit = true; @@ -4354,7 +4356,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) if ( ret == SQLITE_DONE || ret == SQLITE_ROW ) { - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle(), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); } } // BEGIN statement @@ -4370,7 +4372,7 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList &flist, Flags flags ) if ( toCommit ) { // ROLLBACK after some previous error - ( void )exec_sql( QStringLiteral( "ROLLBACK TRANSACTION TO SAVEPOINT \"%1\"" ).arg( savepointId ) ); + ( void )exec_sql( sqliteHandle(), QStringLiteral( "ROLLBACK TRANSACTION TO SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), nullptr, QGS_QUERY_LOG_ORIGIN ); } } else @@ -4402,7 +4404,7 @@ bool QgsSpatiaLiteProvider::createAttributeIndex( int field ) const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle(), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4415,14 +4417,14 @@ bool QgsSpatiaLiteProvider::createAttributeIndex( int field ) .arg( createIndexName( mTableName, fieldName ), mTableName, QgsSqliteUtils::quotedIdentifier( fieldName ) ); - ret = exec_sql( sql, errMsg ); + ret = exec_sql( sqliteHandle(), sql, uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); return false; } - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle(), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4457,7 +4459,7 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id ) const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle(), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4506,7 +4508,7 @@ bool QgsSpatiaLiteProvider::deleteFeatures( const QgsFeatureIds &id ) sqlite3_finalize( stmt ); - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle( ), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4526,7 +4528,7 @@ bool QgsSpatiaLiteProvider::truncate() const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle( ), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4534,14 +4536,14 @@ bool QgsSpatiaLiteProvider::truncate() } sql = QStringLiteral( "DELETE FROM %1" ).arg( QgsSqliteUtils::quotedIdentifier( mTableName ) ); - ret = exec_sql( sql, errMsg ); + ret = exec_sql( sqliteHandle( ), sql, uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); return false; } - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle( ), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4564,7 +4566,7 @@ bool QgsSpatiaLiteProvider::addAttributes( const QList &attributes ) const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle( ), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4577,7 +4579,7 @@ bool QgsSpatiaLiteProvider::addAttributes( const QList &attributes ) .arg( mTableName, iter->name(), iter->typeName() ); - ret = exec_sql( sql, errMsg ); + ret = exec_sql( sqliteHandle( ), sql, uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4585,7 +4587,7 @@ bool QgsSpatiaLiteProvider::addAttributes( const QList &attributes ) } } - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle( ), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4614,7 +4616,7 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle( ), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4781,7 +4783,7 @@ bool QgsSpatiaLiteProvider::changeAttributeValues( const QgsChangedAttributesMap } } - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle(), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -4802,7 +4804,7 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry const QString savepointId { QStringLiteral( "qgis_spatialite_internal_savepoint_%1" ).arg( ++ sSavepointId ) }; - int ret = exec_sql( QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + int ret = exec_sql( sqliteHandle(), QStringLiteral( "SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, QString() ); @@ -4860,7 +4862,7 @@ bool QgsSpatiaLiteProvider::changeGeometryValues( const QgsGeometryMap &geometry sqlite3_finalize( stmt ); - ret = exec_sql( QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), errMsg ); + ret = exec_sql( sqliteHandle(), QStringLiteral( "RELEASE SAVEPOINT \"%1\"" ).arg( savepointId ), uri().uri(), errMsg, QGS_QUERY_LOG_ORIGIN ); if ( ret != SQLITE_OK ) { handleError( sql, errMsg, savepointId ); @@ -6177,7 +6179,7 @@ bool QgsSpatiaLiteProviderMetadata::saveStyle( const QString &uri, const QString ",ui text" ",update_time timestamp DEFAULT CURRENT_TIMESTAMP" ")" ); - ret = sqlite3_exec( sqliteHandle, createQuery.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = QgsSpatiaLiteProvider::exec_sql( sqliteHandle, createQuery.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( SQLITE_OK != ret ) { QgsSqliteHandle::closeDb( handle ); @@ -6269,7 +6271,7 @@ bool QgsSpatiaLiteProviderMetadata::saveStyle( const QString &uri, const QString sql = QStringLiteral( "BEGIN; %1; %2; COMMIT;" ).arg( removeDefaultSql, sql ); } - ret = sqlite3_exec( sqliteHandle, sql.toUtf8().constData(), nullptr, nullptr, &errMsg ); + ret = QgsSpatiaLiteProvider::exec_sql( sqliteHandle, sql.toUtf8().constData(), uri, errMsg, QGS_QUERY_LOG_ORIGIN ); if ( SQLITE_OK != ret ) { QgsSqliteHandle::closeDb( handle ); diff --git a/src/providers/spatialite/qgsspatialiteprovider.h b/src/providers/spatialite/qgsspatialiteprovider.h index 31916a46fc6..42dda06c28a 100644 --- a/src/providers/spatialite/qgsspatialiteprovider.h +++ b/src/providers/spatialite/qgsspatialiteprovider.h @@ -193,6 +193,11 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider */ QgsSqliteHandle *mHandle = nullptr; + /** + * Sqlite exec sql wrapper for SQL logging + */ + static int exec_sql( sqlite3 *handle, const QString &sql, const QString &uri, char *errMsg = nullptr, const QString &origin = QString() ); + private: //! Loads fields from input file to member mAttributeFields @@ -397,11 +402,6 @@ class QgsSpatiaLiteProvider final: public QgsVectorDataProvider */ void handleError( const QString &sql, char *errorMessage, const QString &savepointId ); - /** - * Sqlite exec sql wrapper for SQL logging - */ - int exec_sql( const QString &sql, char *errMsg = nullptr ); - /** * Returns the sqlite handle to be used, if we are inside a transaction it will be the transaction's handle */ diff --git a/src/providers/spatialite/qgsspatialiteproviderconnection.cpp b/src/providers/spatialite/qgsspatialiteproviderconnection.cpp index 6ac8c5937eb..179010b98fc 100644 --- a/src/providers/spatialite/qgsspatialiteproviderconnection.cpp +++ b/src/providers/spatialite/qgsspatialiteproviderconnection.cpp @@ -23,6 +23,7 @@ #include "qgsapplication.h" #include "qgsvectorlayer.h" #include "qgsfeedback.h" +#include "qgsdbquerylog.h" #include #include @@ -466,8 +467,11 @@ void QgsSpatiaLiteProviderConnection::setDefaultCapabilities() QgsAbstractDatabaseProviderConnection::QueryResult QgsSpatiaLiteProviderConnection::executeSqlPrivate( const QString &sql, QgsFeedback *feedback ) const { + QgsDatabaseQueryLogWrapper logWrapper( sql, uri(), providerKey(), QStringLiteral( "QgsSpatiaLiteProviderConnection" ), QGS_QUERY_LOG_ORIGIN ); + if ( feedback && feedback->isCanceled() ) { + logWrapper.setCanceled(); return QgsAbstractDatabaseProviderConnection::QueryResult(); } @@ -478,6 +482,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsSpatiaLiteProviderConnecti if ( feedback && feedback->isCanceled() ) { + logWrapper.setCanceled(); return QgsAbstractDatabaseProviderConnection::QueryResult(); } @@ -540,6 +545,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsSpatiaLiteProviderConnecti if ( ! errCause.isEmpty() ) { + logWrapper.setError( errCause ); throw QgsProviderConnectionException( QObject::tr( "Error executing SQL statement %1: %2" ).arg( sql, errCause ) ); } @@ -562,6 +568,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsSpatiaLiteProviderConnecti if ( !errCause.isEmpty() ) { + logWrapper.setError( errCause ); throw QgsProviderConnectionException( QObject::tr( "Error executing SQL %1: %2" ).arg( sql, errCause ) ); } diff --git a/src/ui/qgsqueryloggerpanelbase.ui b/src/ui/qgsqueryloggerpanelbase.ui new file mode 100644 index 00000000000..7fff61bf082 --- /dev/null +++ b/src/ui/qgsqueryloggerpanelbase.ui @@ -0,0 +1,117 @@ + + + QgsDatabaseQueryLoggerPanelBase + + + + 0 + 0 + 700 + 629 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 24 + 24 + + + + false + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + :/images/themes/default/mActionDeleteSelected.svg:/images/themes/default/mActionDeleteSelected.svg + + + Clear + + + Clear Log + + + + + true + + + + :/images/themes/default/mActionRecord.svg:/images/themes/default/mActionRecord.svg + + + Record Log + + + + + + :/images/themes/default/mActionFileSave.svg:/images/themes/default/mActionFileSave.svg + + + Save Log… + + + + + + QgsPanelWidget + QWidget +
qgspanelwidget.h
+ 1 +
+ + QgsFilterLineEdit + QLineEdit +
qgsfilterlineedit.h
+
+
+ + + + +