Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Better error handling/reporting
  • Loading branch information
elpaso committed Jul 6, 2021
1 parent 40785b5 commit 1a94b07
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 78 deletions.
14 changes: 11 additions & 3 deletions python/gui/auto_generated/qgsqueryresultwidget.sip.in
Expand Up @@ -66,9 +66,10 @@ Returns the sqlVectorLayerOptions
Executes the query
%End

void showError( const QString &title, const QString &message );
void showError( const QString &title, const QString &message, bool isSqlError = false );
%Docstring
Hides the result table and shows the error ``title`` and ``message`` in the message bar.
Hides the result table and shows the error ``title`` and ``message`` in the message bar or
in the SQL error panel is ``isSqlError`` is set.
%End

void tokensReady( const QStringList &tokens );
Expand All @@ -87,7 +88,14 @@ Emitted when a new vector SQL (query) layer must be created.
:param options:
%End

void columnNamesReady();
void firstResultBatchFetched();
%Docstring
Emitted when the first batch of results has been fetched.

.. note::

If the query returns no results this signal is not emitted.
%End

};

Expand Down
5 changes: 4 additions & 1 deletion src/app/browser/qgsinbuiltdataitemproviders.cpp
Expand Up @@ -1076,9 +1076,13 @@ void QgsDatabaseItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *
return;
}
QgsDialog dialog;
dialog.setObjectName( QStringLiteral( "SQLCommandsDialog" ) );
dialog.setWindowTitle( tr( "Run SQL Commands" ) );
QgsGui::enableAutoGeometryRestore( &dialog );
QgsQueryResultWidget *widget { new QgsQueryResultWidget( &dialog, conn2.release() ) };
widget->layout()->setMargin( 0 );
dialog.layout()->addWidget( widget );

connect( widget, &QgsQueryResultWidget::createSqlVectorLayer, widget, [collectionItem]( const QString &, const QString &, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions & options )
{
std::unique_ptr<QgsAbstractDatabaseProviderConnection> conn3( collectionItem->databaseConnection() );
Expand All @@ -1087,7 +1091,6 @@ void QgsDatabaseItemGuiProvider::populateContextMenu( QgsDataItem *item, QMenu *
dialog.exec();
} );
menu->addAction( sqlAction );

}
}
}
Expand Down
167 changes: 97 additions & 70 deletions src/gui/qgsqueryresultwidget.cpp
Expand Up @@ -72,58 +72,51 @@ QgsQueryResultWidget::~QgsQueryResultWidget()
mWorkerThread.quit();
mWorkerThread.wait();
}
cancelRunningQuery();
}

void QgsQueryResultWidget::executeQuery()
{
mQueryResultsTableView->show();
mQueryResultsTableView->hide();
mSqlErrorText->hide();
mFirstRowFetched = false;

// Cancel other threads
if ( mFeedback )
{
mFeedback->cancel();
}

// ... and wait
if ( mQueryResultWatcher.isRunning() )
{
mQueryResultWatcher.waitForFinished();
}
cancelRunningQuery();

if ( mConnection )
{
const auto sql = mSqlEditor->text( );
try
{
mWasCanceled = false;
mFeedback = qgis::make_unique<QgsFeedback>();
mStopButton->setEnabled( true );
mStatusLabel->show();
mStatusLabel->setText( tr( "Running⋯" ) );

connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [ = ]
{
mStatusLabel->setText( tr( "Stopped" ) );
mFeedback->cancel();
mWasCanceled = true;
} );
mWasCanceled = false;
mFeedback = qgis::make_unique<QgsFeedback>();
mStopButton->setEnabled( true );
mStatusLabel->show();
mStatusLabel->setText( tr( "Running⋯" ) );
mSqlErrorMessage.clear();

connect( mStopButton, &QPushButton::pressed, mFeedback.get(), [ = ]
{
mStatusLabel->setText( tr( "Stopped" ) );
mFeedback->cancel();
mWasCanceled = true;
} );

// Create model when result is ready
connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );
// Create model when result is ready
connect( &mQueryResultWatcher, &QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult>::finished, this, &QgsQueryResultWidget::startFetching, Qt::ConnectionType::UniqueConnection );

QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [ = ]() -> QgsAbstractDatabaseProviderConnection::QueryResult
QFuture<QgsAbstractDatabaseProviderConnection::QueryResult> future = QtConcurrent::run( [ = ]() -> QgsAbstractDatabaseProviderConnection::QueryResult
{
try
{
return mConnection->execSql( sql, mFeedback.get() );
} );
mQueryResultWatcher.setFuture( future );

}
catch ( QgsProviderConnectionException &ex )
{
showError( tr( "SQL error" ), ex.what() );
}
}
catch ( QgsProviderConnectionException &ex )
{
mSqlErrorMessage = ex.what();
return QgsAbstractDatabaseProviderConnection::QueryResult();
}
} );
mQueryResultWatcher.setFuture( future );
}
else
{
Expand All @@ -135,7 +128,7 @@ void QgsQueryResultWidget::updateButtons()
{
mFilterToolButton->setEnabled( false );
mExecuteButton->setEnabled( ! mSqlEditor->text().isEmpty() );
mLoadAsNewLayerGroupBox->setVisible( ! mSqlEditor->text().isEmpty() && mConnection && mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::SqlLayers ) );
mLoadAsNewLayerGroupBox->setEnabled( mFirstRowFetched && ! mSqlEditor->text().isEmpty() && mConnection && mConnection->capabilities().testFlag( QgsAbstractDatabaseProviderConnection::Capability::SqlLayers ) );
}

void QgsQueryResultWidget::updateSqlLayerColumns( )
Expand All @@ -153,8 +146,13 @@ void QgsQueryResultWidget::updateSqlLayerColumns( )
for ( const auto &c : constCols )
{
const bool pkCheckedState = hasPkInformation ? mSqlVectorLayerOptions.primaryKeyColumns.contains( c ) : c.contains( QStringLiteral( "id" ), Qt::CaseSensitivity::CaseInsensitive );
mPkColumnsComboBox->addItemWithCheckState( c, pkCheckedState ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
// Only check first match
mPkColumnsComboBox->addItemWithCheckState( c, pkCheckedState && mPkColumnsComboBox->checkedItems().isEmpty() ? Qt::CheckState::Checked : Qt::CheckState::Unchecked );
mGeometryColumnComboBox->addItem( c );
if ( ! hasGeomColInformation && geomColCandidates.contains( c, Qt::CaseSensitivity::CaseInsensitive ) )
{
mGeometryColumnComboBox->setCurrentText( c );
}
}
mPkColumnsCheckBox->setChecked( hasPkInformation );
mGeometryColumnCheckBox->setChecked( hasGeomColInformation );
Expand All @@ -164,58 +162,87 @@ void QgsQueryResultWidget::updateSqlLayerColumns( )
}
}

void QgsQueryResultWidget::cancelRunningQuery()
{
// Cancel other threads
if ( mFeedback )
{
mFeedback->cancel();
}

// ... and wait
if ( mQueryResultWatcher.isRunning() )
{
mQueryResultWatcher.waitForFinished();
}
}

void QgsQueryResultWidget::startFetching()
{
if ( ! mWasCanceled )
{
QgsAbstractDatabaseProviderConnection::QueryResult result { mQueryResultWatcher.result() };
mModel = qgis::make_unique<QgsQueryResultModel>( result );
connect( mFeedback.get(), &QgsFeedback::canceled, mModel.get(), [ = ]
if ( ! mSqlErrorMessage.isEmpty() )
{
mModel->cancel();
mWasCanceled = true;
} );

connect( mModel.get(), &QgsQueryResultModel::rowsInserted, this, [ = ]( const QModelIndex &, int, int )
showError( tr( "SQL error" ), mSqlErrorMessage, true );
}
else
{
if ( ! mFirstRowFetched )
QgsAbstractDatabaseProviderConnection::QueryResult result { mQueryResultWatcher.result() };
mModel = qgis::make_unique<QgsQueryResultModel>( result );
connect( mFeedback.get(), &QgsFeedback::canceled, mModel.get(), [ = ]
{
emit columnNamesReady();
mFirstRowFetched = true;
updateButtons();
updateSqlLayerColumns( );
}
mStatusLabel->setText( tr( "Fetched rows: %1 %2" )
.arg( mModel->rowCount( mModel->index( -1, -1 ) ) )
.arg( mWasCanceled ? tr( "(stopped)" ) : QString() ) );
} );
mQueryResultsTableView->setModel( mModel.get() );
mQueryResultsTableView->show();
mModel->cancel();
mWasCanceled = true;
} );

connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [ = ]
{
mStopButton->setEnabled( false );
if ( ! mWasCanceled )
connect( mModel.get(), &QgsQueryResultModel::rowsInserted, this, [ = ]( const QModelIndex &, int, int )
{
mStatusLabel->setText( "Query executed successfully." );
}
} );
if ( ! mFirstRowFetched )
{
emit firstResultBatchFetched();
mFirstRowFetched = true;
mQueryResultsTableView->show();
updateButtons();
updateSqlLayerColumns( );
}
mStatusLabel->setText( tr( "Fetched rows: %1 %2" )
.arg( mModel->rowCount( mModel->index( -1, -1 ) ) )
.arg( mWasCanceled ? tr( "(stopped)" ) : QString() ) );
} );

mQueryResultsTableView->setModel( mModel.get() );
mQueryResultsTableView->show();

connect( mModel.get(), &QgsQueryResultModel::fetchingComplete, mStopButton, [ = ]
{
mStopButton->setEnabled( false );
if ( ! mWasCanceled )
{
mStatusLabel->setText( "Query executed successfully." );
}
} );
}
}
else
{
mStatusLabel->setText( tr( "SQL command aborted" ) );
}

}

void QgsQueryResultWidget::showError( const QString &title, const QString &message )
void QgsQueryResultWidget::showError( const QString &title, const QString &message, bool isSqlError )
{
mStatusLabel->show();
mStatusLabel->setText( tr( "There was an error executing the query." ) );
mQueryResultsTableView->hide();
mSqlErrorText->show();
mSqlErrorText->setText( message );
mMessageBar->pushCritical( title, message );
if ( isSqlError )
{
mSqlErrorText->show();
mSqlErrorText->setText( message );
}
else
{
mMessageBar->pushCritical( title, message );
}
}

void QgsQueryResultWidget::tokensReady( const QStringList &tokens )
Expand Down
21 changes: 18 additions & 3 deletions src/gui/qgsqueryresultwidget.h
Expand Up @@ -122,9 +122,10 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
void executeQuery();

/**
* Hides the result table and shows the error \a title and \a message in the message bar.
* Hides the result table and shows the error \a title and \a message in the message bar or
* in the SQL error panel is \a isSqlError is set.
*/
void showError( const QString &title, const QString &message );
void showError( const QString &title, const QString &message, bool isSqlError = false );

/**
* Triggered when the threaded API fetcher has new \a tokens to add.
Expand All @@ -145,7 +146,11 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
*/
void createSqlVectorLayer( const QString &providerKey, const QString &connectionUri, const QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions &options );

void columnNamesReady();
/**
* Emitted when the first batch of results has been fetched.
* \note If the query returns no results this signal is not emitted.
*/
void firstResultBatchFetched();

private:

Expand All @@ -158,14 +163,24 @@ class GUI_EXPORT QgsQueryResultWidget: public QWidget, private Ui::QgsQueryResul
QgsAbstractDatabaseProviderConnection::SqlVectorLayerOptions mSqlVectorLayerOptions;
bool mFirstRowFetched = false;
QFutureWatcher<QgsAbstractDatabaseProviderConnection::QueryResult> mQueryResultWatcher;
QString mSqlErrorMessage;

/**
* Updates buttons status
*/
void updateButtons();

/**
* Updates SQL layer columns
*/
void updateSqlLayerColumns();

/**
* Cancel and wait for finish.
*/
void cancelRunningQuery();


/**
* Starts the model population after initial query run
*/
Expand Down
2 changes: 1 addition & 1 deletion tests/src/gui/testqgsqueryresultwidget.cpp
Expand Up @@ -120,7 +120,7 @@ void TestQgsQueryResultWidget::testWidget()
//d->exec();
w->executeQuery();
bool exited = false;
connect( w, &QgsQueryResultWidget::columnNamesReady, d.get(), [ & ] { exited = true; } );
connect( w, &QgsQueryResultWidget::firstResultBatchFetched, d.get(), [ & ] { exited = true; } );
while ( ! exited )
QgsApplication::processEvents();
const auto rowCount { w->mModel->rowCount( w->mModel->index( -1, -1 ) ) };
Expand Down

0 comments on commit 1a94b07

Please sign in to comment.