Merge pull request #48107 from elpaso/db_logger_2

Implementation of QEP: Add SQL Logging in the debugging/development panel
This commit is contained in:
Alessandro Pasotti 2022-04-28 10:11:31 +02:00 committed by GitHub
commit 041e18a85c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 3276 additions and 923 deletions

View File

@ -591,6 +591,7 @@
<file>themes/default/propertyicons/attributes.svg</file>
<file>themes/default/propertyicons/CRS.svg</file>
<file>themes/default/propertyicons/datadefined.svg</file>
<file>themes/default/propertyicons/database.svg</file>
<file>themes/default/propertyicons/diagram.svg</file>
<file>themes/default/propertyicons/digitizing.svg</file>
<file>themes/default/propertyicons/display.svg</file>

View File

@ -0,0 +1 @@
<svg height="24" viewBox="0 0 6.34992 6.34992" width="24" xmlns="http://www.w3.org/2000/svg"><g transform="scale(.99975 .99997)"><path d="m.53 1.455h5.29v4.022c-1.973.87-3.596.76-5.293 0z" fill="#6d97c4"/><path d="m.53 1.244v4.18l.53.265-.002-4.446z" fill="#bacee3"/><path d="m5.822 1.323v4.1l-1.058.398v-4.497z" fill="#55769a"/><path d="m5.822 1.19h.265v3.943h-.265zm-5.557 0h.265v3.943h-.266z" fill="#415a75"/><path d="m.53 2.46v.45c.21.146.41.226.61.288.52.158 1.256.242 2.036.242s1.47-.087 1.99-.245c.24-.082.472-.15.656-.285v-.45c0 .065-.046.164-.17.25-.124.09-.313.168-.557.242-.49.147-1.16.223-1.92.223s-1.474-.075-1.96-.223c-.246-.07-.436-.15-.56-.24-.123-.088-.126-.188-.126-.25z" fill="#415a75"/><ellipse cx="3.176" cy="1.191" fill="#bacee3" rx="2.779" ry=".794" stroke="#415a75" stroke-linecap="round" stroke-linejoin="round" stroke-width=".265"/><path d="m.265 5.133c0 .17.095.328.254.44.155.115.392.21.652.29.52.16 1.224.223 2.004.223s1.474-.062 1.998-.222c.26-.078.494-.176.653-.29.156-.113.26-.27.26-.44h-.265s-.008.155-.14.245c-.125.09-.315.17-.56.244-.49.148-1.186.197-1.946.197s-1.446-.046-1.933-.195c-.242-.072-.432-.152-.556-.245-.123-.085-.156-.184-.156-.247zm.265-1.35v.45c.21.146.41.225.612.287.52.158 1.254.243 2.034.243s1.47-.087 1.99-.245c.24-.083.472-.15.656-.285v-.45c0 .065-.046.164-.17.25-.124.09-.313.168-.557.242-.49.147-1.16.223-1.92.223s-1.473-.075-1.96-.224c-.244-.07-.434-.15-.558-.24-.124-.087-.128-.187-.128-.25z" fill="#415a75"/></g></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -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();

View File

@ -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 *
************************************************************************/

View File

@ -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

View File

@ -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

View File

@ -147,24 +147,24 @@ void QgsNetworkLogger::requestEncounteredSslErrors( int requestId, const QList<Q
emit dataChanged( requestIndex, requestIndex );
}
QgsNetworkLoggerNode *QgsNetworkLogger::index2node( const QModelIndex &index ) const
QgsDevToolsModelNode *QgsNetworkLogger::index2node( const QModelIndex &index ) const
{
if ( !index.isValid() )
return mRootNode.get();
return reinterpret_cast<QgsNetworkLoggerNode *>( index.internalPointer() );
return reinterpret_cast<QgsDevToolsModelNode *>( index.internalPointer() );
}
QList<QAction *> 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<int> &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 )

View File

@ -20,7 +20,7 @@
#include <QElapsedTimer>
#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;

View File

@ -26,89 +26,12 @@
#include <QClipboard>
#include <nlohmann/json.hpp>
//
// QgsNetworkLoggerNode
//
QgsNetworkLoggerNode::QgsNetworkLoggerNode() = default;
QgsNetworkLoggerNode::~QgsNetworkLoggerNode() = default;
//
// QgsNetworkLoggerGroup
//
QgsNetworkLoggerGroup::QgsNetworkLoggerGroup( const QString &title )
: mGroupTitle( title )
{
}
void QgsNetworkLoggerGroup::addChild( std::unique_ptr<QgsNetworkLoggerNode> 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<QgsNetworkLoggerNode> &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<QAction *> 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<QPair<QString, QString> > 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<QByteArray> headers = request.request().rawHeaderList();
for ( const QByteArray &header : headers )
@ -567,7 +432,7 @@ QgsNetworkLoggerRequestHeadersGroup::QgsNetworkLoggerRequestHeadersGroup( const
//
QgsNetworkLoggerPostContentGroup::QgsNetworkLoggerPostContentGroup( const QgsNetworkRequestParameters &parameters )
: 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<QByteArray> headers = reply.rawHeaderList();
for ( const QByteArray &header : headers )
@ -621,7 +486,7 @@ QgsNetworkLoggerReplyHeadersGroup::QgsNetworkLoggerReplyHeadersGroup( const QgsN
// QgsNetworkLoggerSslErrorGroup
//
QgsNetworkLoggerSslErrorGroup::QgsNetworkLoggerSslErrorGroup( const QList<QSslError> &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<QAction *> QgsNetworkLoggerNode::actions( QObject * )
{
return QList< QAction * >();
}
QVariant QgsNetworkLoggerNode::toVariant() const
{
return QVariant();
return QgsDevToolsModelGroup::data( role );
}

View File

@ -16,6 +16,7 @@
#define QGSNETWORKLOGGERNODE_H
#include "qgsnetworkaccessmanager.h"
#include "devtools/qgsdevtoolsmodelnode.h"
#include <QElapsedTimer>
#include <QVariant>
#include <QColor>
@ -24,160 +25,6 @@
#include <deque>
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:

View File

@ -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 <QUrlQuery>
#include <QColor>
#include <QBrush>
#include <QFont>
#include <QAction>
#include <QDesktopServices>
#include <QApplication>
#include <QClipboard>
#include <nlohmann/json.hpp>
//
// QgsDevToolsModelNode
//
QgsDevToolsModelNode::QgsDevToolsModelNode() = default;
QgsDevToolsModelNode::~QgsDevToolsModelNode() = default;
QVariant QgsDevToolsModelNode::toVariant() const
{
return QVariant();
}
QList<QAction *> QgsDevToolsModelNode::actions( QObject * )
{
return QList< QAction * >();
}
//
// QgsDevToolsModelGroup
//
QgsDevToolsModelGroup::QgsDevToolsModelGroup( const QString &title )
: mGroupTitle( title )
{
}
void QgsDevToolsModelGroup::addChild( std::unique_ptr<QgsDevToolsModelNode> 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<QgsDevToolsModelNode> &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<QAction *> 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 ) );
}

View File

@ -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 <QElapsedTimer>
#include <QVariant>
#include <QColor>
#include <QUrl>
#include <memory>
#include <deque>
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

View File

@ -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 <QThread>
#include <QApplication>
#include <QUrlQuery>
QgsAppQueryLogger::QgsAppQueryLogger( QObject *parent )
: QAbstractItemModel( parent )
, mRootNode( std::make_unique< QgsDatabaseQueryLoggerRootNode >() )
{
// logger must be created on the main thread
Q_ASSERT( QThread::currentThread() == QApplication::instance()->thread() );
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<QgsDevToolsModelNode *>( index.internalPointer() );
}
QList<QAction *> 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<int> &rows )
{
QList< int > res = rows;
std::sort( res.begin(), res.end(), std::greater< int >() );
for ( int row : std::as_const( res ) )
{
int popId = data( index( row, 0, QModelIndex() ), 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<QgsDevToolsModelValueNode *>( request->childAt( i ) ); valueNode->value().contains( mFilterString, Qt::CaseInsensitive ) )
{
return true;
}
}
return false;
}
}
return true;
}

View File

@ -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 <QAbstractItemModel>
#include <QSortFilterProxyModel>
#include <QElapsedTimer>
#include "qgsdbquerylog.h"
#include <memory>
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

View File

@ -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 <QUrlQuery>
#include <QColor>
#include <QBrush>
#include <QFont>
#include <QAction>
#include <QDesktopServices>
#include <QApplication>
#include <QClipboard>
#include <nlohmann/json.hpp>
//
// 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 <br/> 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<br/>%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<QAction *> 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;
}

View File

@ -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 <QElapsedTimer>
#include <QVariant>
#include <QColor>
#include <QUrl>
#include <memory>
#include <deque>
#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

View File

@ -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 <QFileDialog>
#include <QFontDatabase>
#include <QMenu>
#include <QMessageBox>
#include <QScrollBar>
#include <QToolButton>
#include <QCheckBox>
#include <QTextStream>
#include <nlohmann/json.hpp>
//
// QgsDatabaseQueryLoggerTreeView
//
QgsDatabaseQueryLoggerTreeView::QgsDatabaseQueryLoggerTreeView( QgsAppQueryLogger *logger, QWidget *parent )
: QTreeView( parent )
, mLogger( logger )
{
connect( this, &QTreeView::expanded, this, &QgsDatabaseQueryLoggerTreeView::itemExpanded );
setFont( QFontDatabase::systemFont( QFontDatabase::FixedFont ) );
mProxyModel = new QgsDatabaseQueryLoggerProxyModel( mLogger, this );
setModel( mProxyModel );
setContextMenuPolicy( Qt::CustomContextMenu );
connect( this, &QgsDatabaseQueryLoggerTreeView::customContextMenuRequested, this, &QgsDatabaseQueryLoggerTreeView::contextMenu );
connect( verticalScrollBar(), &QAbstractSlider::sliderMoved, this, [this]( int value )
{
if ( value == verticalScrollBar()->maximum() )
mAutoScroll = true;
else
mAutoScroll = false;
} );
connect( mLogger, &QAbstractItemModel::rowsInserted, this, [ = ]
{
if ( mLogger->rowCount() > ( QgsAppQueryLogger::MAX_LOGGED_REQUESTS * 1.2 ) ) // 20 % more as buffer
{
// never trim expanded nodes
const int toTrim = mLogger->rowCount() - QgsAppQueryLogger::MAX_LOGGED_REQUESTS;
int trimmed = 0;
QList< int > rowsToTrim;
rowsToTrim.reserve( toTrim );
for ( int i = 0; i < mLogger->rowCount(); ++i )
{
const QModelIndex proxyIndex = mProxyModel->mapFromSource( mLogger->index( i, 0 ) );
if ( !proxyIndex.isValid() || !isExpanded( proxyIndex ) )
{
rowsToTrim << i;
trimmed++;
}
if ( trimmed == toTrim )
break;
}
mLogger->removeRequestRows( rowsToTrim );
}
if ( mAutoScroll )
scrollToBottom();
} );
mMenu = new QMenu( this );
}
void QgsDatabaseQueryLoggerTreeView::setFilterString( const QString &string )
{
mProxyModel->setFilterString( string );
}
void QgsDatabaseQueryLoggerTreeView::itemExpanded( const QModelIndex &index )
{
// if the item is a QgsNetworkLoggerRequestGroup item, open all children (show ALL info of it)
// we want to scroll to last request
// only expand all children on QgsNetworkLoggerRequestGroup nodes (which don't have a valid parent!)
if ( !index.parent().isValid() )
expandChildren( index );
// make ALL request information visible by scrolling view to it
scrollTo( index );
}
void QgsDatabaseQueryLoggerTreeView::contextMenu( QPoint point )
{
const QModelIndex viewModelIndex = indexAt( point );
const QModelIndex modelIndex = mProxyModel->mapToSource( viewModelIndex );
if ( modelIndex.isValid() )
{
mMenu->clear();
const QList< QAction * > actions = mLogger->actions( modelIndex, mMenu );
mMenu->addActions( actions );
if ( !mMenu->actions().empty() )
{
mMenu->exec( viewport()->mapToGlobal( point ) );
}
}
}
void QgsDatabaseQueryLoggerTreeView::expandChildren( const QModelIndex &index )
{
if ( !index.isValid() )
return;
const int count = model()->rowCount( index );
for ( int i = 0; i < count; ++i )
{
const QModelIndex childIndex = model()->index( i, 0, index );
expandChildren( childIndex );
}
if ( !isExpanded( index ) )
expand( index );
}
//
// QgsDatabaseQueryLoggerPanelWidget
//
QgsDatabaseQueryLoggerPanelWidget::QgsDatabaseQueryLoggerPanelWidget( QgsAppQueryLogger *logger, QWidget *parent )
: QgsDevToolWidget( parent )
, mLogger( logger )
{
setupUi( this );
mTreeView = new QgsDatabaseQueryLoggerTreeView( mLogger );
verticalLayout->addWidget( mTreeView );
mToolbar->setIconSize( QgsGuiUtils::iconSize( true ) );
mFilterLineEdit->setShowClearButton( true );
mFilterLineEdit->setShowSearchIcon( true );
mFilterLineEdit->setPlaceholderText( tr( "Filter 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 );
}

View File

@ -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 <QTreeView>
class QgsAppQueryLogger;
class QgsDatabaseQueryLoggerProxyModel;
/**
* \ingroup app
* \class QgsDatabaseQueryLoggerTreeView
* \brief A custom QTreeView subclass for showing logged database queries.
*/
class QgsDatabaseQueryLoggerTreeView: public QTreeView
{
Q_OBJECT
public:
/**
* Constructor for QgsDatabaseQueryLoggerTreeView, attached to the specified \a logger.
*/
QgsDatabaseQueryLoggerTreeView( QgsAppQueryLogger *logger, QWidget *parent = nullptr );
public slots:
/**
* Sets a filter \a string to apply to request URLs.
*/
void setFilterString( const QString &string );
private slots:
void itemExpanded( const QModelIndex &index );
void contextMenu( QPoint point );
private:
void expandChildren( const QModelIndex &index );
QMenu *mMenu = nullptr;
QgsAppQueryLogger *mLogger = nullptr;
QgsDatabaseQueryLoggerProxyModel *mProxyModel = nullptr;
bool mAutoScroll = true;
};
/**
* \ingroup app
* \class QgsDatabaseQueryLoggerPanelWidget
* \brief A panel widget showing logged network requests.
*/
class QgsDatabaseQueryLoggerPanelWidget : public QgsDevToolWidget, private Ui::QgsDatabaseQueryLoggerPanelBase
{
Q_OBJECT
public:
/**
* Constructor for QgsDatabaseQueryLoggerPanelWidget, linked with the specified \a logger.
*/
QgsDatabaseQueryLoggerPanelWidget( QgsAppQueryLogger *logger, QWidget *parent );
private:
QgsDatabaseQueryLoggerTreeView *mTreeView = nullptr;
QgsAppQueryLogger *mLogger = nullptr;
};
#endif // QGSQUERYLOGGERPANELWIDGET_H

View File

@ -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 );
}

View File

@ -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

View File

@ -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();

View File

@ -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;

View File

@ -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

View File

@ -28,6 +28,7 @@
#include "qgsfeedback.h"
#include "qgsogrutils.h"
#include "qgsfielddomain.h"
#include "qgsdbquerylog.h"
#include <QTextCodec>
#include <QRegularExpression>
@ -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<std::chrono::milliseconds>( 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 ) );
}

View File

@ -77,6 +77,7 @@
#include "qgslocator.h"
#include "qgsreadwritelocker.h"
#include "qgsbabelformatregistry.h"
#include "qgsdbquerylog.h"
#include "gps/qgsgpsconnectionregistry.h"
#include "processing/qgsprocessingregistry.h"
@ -265,6 +266,7 @@ void QgsApplication::init( QString profileFolder )
std::call_once( sMetaTypesRegistered, []
{
qRegisterMetaType<QgsGeometry::Error>( "QgsGeometry::Error" );
qRegisterMetaType<QgsDatabaseQueryLogEntry>( "QgsDatabaseQueryLogEntry" );
qRegisterMetaType<QgsProcessingFeatureSourceDefinition>( "QgsProcessingFeatureSourceDefinition" );
qRegisterMetaType<QgsProcessingOutputLayerDefinition>( "QgsProcessingOutputLayerDefinition" );
qRegisterMetaType<QgsUnitTypes::LayoutUnit>( "QgsUnitTypes::LayoutUnit" );
@ -2433,6 +2435,11 @@ QgsRecentStyleHandler *QgsApplication::recentStyleHandler()
return members()->mRecentStyleHandler;
}
QgsDatabaseQueryLog *QgsApplication::databaseQueryLog()
{
return members()->mQueryLogger;
}
QgsStyleModel *QgsApplication::defaultStyleModel()
{
return members()->mStyleModel;
@ -2512,6 +2519,11 @@ QgsApplication::ApplicationMembers::ApplicationMembers()
mMessageLog = new QgsMessageLog();
QgsRuntimeProfiler *profiler = QgsRuntimeProfiler::threadLocalInstance();
{
profiler->start( tr( "Create query logger" ) );
mQueryLogger = new QgsDatabaseQueryLog();
profiler->end();
}
{
profiler->start( tr( "Setup coordinate reference system registry" ) );
mCrsRegistry = new QgsCoordinateReferenceSystemRegistry();
@ -2726,6 +2738,7 @@ QgsApplication::ApplicationMembers::~ApplicationMembers()
delete mConnectionRegistry;
delete mLocalizedDataPathRegistry;
delete mCrsRegistry;
delete mQueryLogger;
delete mSettingsRegistryCore;
}

View File

@ -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;

View File

@ -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 <QDateTime>
//
// 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 );
}

270
src/core/qgsdbquerylog.h Normal file
View File

@ -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 <QString>
#include <QDateTime>
/**
* \ingroup core
* \class QgsDatabaseQueryLogEntry
* \brief Encapsulates a logged database query.
*
* \since QGIS 3.24
*/
class CORE_EXPORT QgsDatabaseQueryLogEntry
{
public:
/**
* Constructor for QgsDatabaseQueryLogEntry.
*/
QgsDatabaseQueryLogEntry( const QString &query = QString() );
/**
* Unique query ID.
*
* This ID will automatically be set on creation of a new QgsDatabaseQueryLogEntry object.
*/
int queryId = 0;
//! 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

View File

@ -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<QgsDatabaseQueryLogWrapper> logWrapper = std::make_unique<QgsDatabaseQueryLogWrapper>( 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<QgsMssqlTransaction *>( p->transaction() )->conn() : std::shared_ptr<QgsMssqlDatabase>() )
, mConnInfo( p->uri().uri( ) )
{}
QgsFeatureIterator QgsMssqlFeatureSource::getFeatures( const QgsFeatureRequest &request )
{
return QgsFeatureIterator( new QgsMssqlFeatureIterator( this, false, request ) );
}
const QString &QgsMssqlFeatureSource::connInfo() const
{
return mConnInfo;
}

View File

@ -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;
};

View File

@ -20,6 +20,7 @@
#include "qgsmssqldatabase.h"
#include "qgsmssqlproviderconnection.h"
#include "qgsfeedback.h"
#include "qgsdbquerylog.h"
#include <QtGlobal>
#include <QFileInfo>
@ -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<QString> 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<QVariant> 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<QgsField> &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 <QgsDatabaseQueryLogWrapper> logWrapper = std::make_unique<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;
@ -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() )

View File

@ -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<int, QString> 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

View File

@ -27,6 +27,8 @@
#include "qgsvariantutils.h"
#include <QSqlError>
#include <QSqlField>
#include <QSqlDriver>
QMap<QString, QgsOracleConn *> 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<QString, QVariant> 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 &params )
{
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 &params, 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 &param : 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<int> 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;
}

View File

@ -31,6 +31,7 @@
#include "qgslogger.h"
#include "qgsdatasourceuri.h"
#include "qgsvectordataprovider.h"
#include "qgsdbquerylog.h"
#include <QSqlDatabase>
#include <QSqlQuery>
@ -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 &params );
bool execLogged( QSqlQuery &qry, const QString &sql, const QVariantList &params, const QString &originatorClass = QString(), const QString &queryOrigin = QString() );
//! reference count
int mRef;
@ -280,6 +292,9 @@ class QgsOracleConn : public QObject
static QMap<QString, QgsOracleConn *> sConnections;
static int snConnections;
static QMap<QString, QDateTime> sBrokenConnections;
// Connection URI string representation for query logger
QString mConnInfo;
};
#endif

View File

@ -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<QgsDataItem *> QgsOracleRootItem::createChildren()
{
QVector<QgsDataItem *> 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 );
}

View File

@ -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 )

View File

@ -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<QVariant> 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<QgsField> &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() )
{

View File

@ -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;

View File

@ -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() )
{

View File

@ -61,6 +61,7 @@ bool QgsOracleTableModel::searchableColumn( int column ) const
case DbtmSelectAtId:
return false;
}
return false;
}
void QgsOracleTableModel::addTableEntry( const QgsOracleLayerProperty &layerProperty )

View File

@ -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 )

View File

@ -31,6 +31,7 @@
#include "qgspostgresstringutils.h"
#include "qgspostgresconnpool.h"
#include "qgsvariantutils.h"
#include "qgsdbquerylog.h"
#include <QApplication>
#include <QStringList>
@ -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<QgsPostgresSchemaProperty> &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<QgsDatabaseQueryLogWrapper> logWrapper = std::make_unique<QgsDatabaseQueryLogWrapper>( 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<QgsPostgresLayerProperty *> &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 )
{

View File

@ -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;

View File

@ -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,

View File

@ -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<int>::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 );
}

View File

@ -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;

View File

@ -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<Oid, QMap<int, QString> > fmtFieldTypeMap, descrMap, defValMap, identityMap, generatedMap;
QMap<Oid, QMap<int, Oid> > 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<Oid, PGTypeInfo> 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 &quotedColNames )
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<QVariant> 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<QgsField> &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<QgsField> &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<QgsRelation> 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 )

View File

@ -257,7 +257,7 @@ QgsAbstractDatabaseProviderConnection::QueryResult QgsPostgresProviderConnection
}
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
std::unique_ptr<QgsPostgresResult> res = std::make_unique<QgsPostgresResult>( conn->PQexec( sql ) );
std::unique_ptr<QgsPostgresResult> res = std::make_unique<QgsPostgresResult>( conn->LoggedPQexec( "QgsPostgresProviderConnection", sql ) );
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
results.setQueryExecutionTime( std::chrono::duration_cast<std::chrono::milliseconds>( end - begin ).count() );

View File

@ -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 )
{

View File

@ -235,6 +235,10 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
sqliteStatement = nullptr;
close();
}
else
{
mQueryLogWrapper = std::make_unique<QgsDatabaseQueryLogWrapper>( 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 )
{

View File

@ -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<QgsDatabaseQueryLogWrapper> mQueryLogWrapper;
};
#endif // QGSSPATIALITEFEATUREITERATOR_H

View File

@ -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<QgsField> &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<QgsField> &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<QgsField> &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 );

View File

@ -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
*/

View File

@ -23,6 +23,7 @@
#include "qgsapplication.h"
#include "qgsvectorlayer.h"
#include "qgsfeedback.h"
#include "qgsdbquerylog.h"
#include <QRegularExpression>
#include <QTextCodec>
@ -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 ) );
}

View File

@ -0,0 +1,117 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsDatabaseQueryLoggerPanelBase</class>
<widget class="QgsPanelWidget" name="QgsDatabaseQueryLoggerPanelBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>629</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolBar" name="mToolbar">
<property name="iconSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="floatable">
<bool>false</bool>
</property>
<addaction name="mActionRecord"/>
<addaction name="mActionClear"/>
<addaction name="mActionSaveLog"/>
<addaction name="separator"/>
</widget>
</item>
<item>
<widget class="QgsFilterLineEdit" name="mFilterLineEdit"/>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
</layout>
<action name="mActionClear">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionDeleteSelected.svg</normaloff>:/images/themes/default/mActionDeleteSelected.svg</iconset>
</property>
<property name="text">
<string>Clear</string>
</property>
<property name="toolTip">
<string>Clear Log</string>
</property>
</action>
<action name="mActionRecord">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionRecord.svg</normaloff>:/images/themes/default/mActionRecord.svg</iconset>
</property>
<property name="text">
<string>Record Log</string>
</property>
</action>
<action name="mActionSaveLog">
<property name="icon">
<iconset resource="../../images/images.qrc">
<normaloff>:/images/themes/default/mActionFileSave.svg</normaloff>:/images/themes/default/mActionFileSave.svg</iconset>
</property>
<property name="text">
<string>Save Log…</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>QgsPanelWidget</class>
<extends>QWidget</extends>
<header>qgspanelwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsFilterLineEdit</class>
<extends>QLineEdit</extends>
<header>qgsfilterlineedit.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../images/images.qrc"/>
</resources>
<connections/>
</ui>