Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
QgsQueryResultWidget
  • Loading branch information
elpaso committed Jul 6, 2021
1 parent 81ecf97 commit d914a50
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 4 deletions.
7 changes: 7 additions & 0 deletions python/gui/auto_generated/codeeditors/qgscodeeditorsql.sip.in
Expand Up @@ -36,6 +36,13 @@ Constructor for QgsCodeEditorSQL
Set field names to be added to the lexer API.

.. versionadded:: 3.14
%End

void setFieldNames( const QStringList &fieldNames );
%Docstring
Set field names to ``fieldNames`` to be added to the lexer API.

.. versionadded:: 3.18
%End

protected:
Expand Down
1 change: 1 addition & 0 deletions python/gui/gui_auto.sip
Expand Up @@ -175,6 +175,7 @@
%Include auto_generated/qgsprovidersourcewidgetproviderregistry.sip
%Include auto_generated/qgsproxystyle.sip
%Include auto_generated/qgsquerybuilder.sip
%Include auto_generated/qgsqueryresultwidget.sip
%Include auto_generated/qgsrangeslider.sip
%Include auto_generated/qgsrasterformatsaveoptionswidget.sip
%Include auto_generated/qgsrasterlayersaveasdialog.sip
Expand Down
3 changes: 3 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -570,6 +570,7 @@ set(QGIS_GUI_SRCS
qgsproviderconnectioncombobox.cpp
qgsproxystyle.cpp
qgsquerybuilder.cpp
qgsqueryresultwidget.cpp
qgsrangeslider.cpp
qgsrasterformatsaveoptionswidget.cpp
qgsrasterlayersaveasdialog.cpp
Expand Down Expand Up @@ -819,6 +820,7 @@ set(QGIS_GUI_HDRS
qgsprovidersourcewidgetproviderregistry.h
qgsproxystyle.h
qgsquerybuilder.h
qgsqueryresultwidget.h
qgsrangeslider.h
qgsrasterformatsaveoptionswidget.h
qgsrasterlayersaveasdialog.h
Expand Down Expand Up @@ -1322,6 +1324,7 @@ set(QGIS_GUI_UI_HDRS
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsprojectionselectorbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsrasterlayerpropertiesbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsquerybuilderbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgsqueryresultwidgetbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssqlcomposerdialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgssublayersdialogbase.h
${CMAKE_CURRENT_BINARY_DIR}/../ui/ui_qgstablewidgetuibase.h
Expand Down
16 changes: 12 additions & 4 deletions src/gui/codeeditors/qgscodeeditorsql.cpp
Expand Up @@ -71,14 +71,16 @@ void QgsCodeEditorSQL::initializeLexer()

void QgsCodeEditorSQL::setFields( const QgsFields &fields )
{
mFieldNames.clear();

for ( const QgsField &field : fields )
QStringList fieldNames;

for ( const QgsField &field : qgis::as_const( fields ) )
{
mFieldNames << field.name();
fieldNames.push_back( field.name() );
}

updateApis();
setFieldNames( fieldNames );

}

void QgsCodeEditorSQL::updateApis()
Expand All @@ -93,3 +95,9 @@ void QgsCodeEditorSQL::updateApis()
mApis->prepare();
mSqlLexer->setAPIs( mApis );
}

void QgsCodeEditorSQL::setFieldNames( const QStringList &fieldNames )
{
mFieldNames = fieldNames;
updateApis();
}
7 changes: 7 additions & 0 deletions src/gui/codeeditors/qgscodeeditorsql.h
Expand Up @@ -46,6 +46,13 @@ class GUI_EXPORT QgsCodeEditorSQL : public QgsCodeEditor
*/
void setFields( const QgsFields &fields );

/**
* Set field names to \a fieldNames to be added to the lexer API.
*
* \since QGIS 3.18
*/
void setFieldNames( const QStringList &fieldNames );

protected:
void initializeLexer() override;

Expand Down
79 changes: 79 additions & 0 deletions src/ui/qgsqueryresultwidgetbase.ui
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QgsQueryResultWidgetBase</class>
<widget class="QWidget" name="QgsQueryResultWidgetBase">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>662</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string notr="true">Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QgsCodeEditorSQL" name="mCodeEditor" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="mExecuteButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Execute</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="mStopButton">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="mQueryResultsTableView"/>
</item>
<item>
<widget class="QLabel" name="mStatusLabel">
<property name="text">
<string>Status and errors goes here.</string>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsCodeEditorSQL</class>
<extends>QWidget</extends>
<header>qgscodeeditorsql.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
1 change: 1 addition & 0 deletions tests/src/gui/CMakeLists.txt
Expand Up @@ -67,6 +67,7 @@ set(TESTS
testqgsmeshlayerpropertiesdialog.cpp
testqgsexternalresourcewidgetwrapper.cpp
testqgsquerybuilder.cpp
testqgsqueryresultwidget.cpp
)

foreach(TESTSRC ${TESTS})
Expand Down
107 changes: 107 additions & 0 deletions tests/src/gui/testqgsqueryresultwidget.cpp
@@ -0,0 +1,107 @@
/***************************************************************************
testqgsqueryresultwidget.cpp
----------------------
Date : Jan 2021
Copyright : (C) 2021 Alessandro Pasotti
Email : elpaso at itopen dot it
***************************************************************************
* *
* 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 "qgstest.h"

#include "qgsqueryresultwidget.h"
#include "qgsqueryresultmodel.h"
#include "qgsproviderregistry.h"
#include "qgsprovidermetadata.h"
#include "qgsabstractdatabaseproviderconnection.h"
#include <QApplication>
#include <QAction>
#include <QDialog>
#include <QVBoxLayout>

class TestQgsQueryResultWidget: public QObject
{
Q_OBJECT
private slots:
void initTestCase(); // will be called before the first testfunction is executed.
void cleanupTestCase(); // will be called after the last testfunction was executed.
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void testWidgetCrash();
void testWidget();

private:

std::unique_ptr<QgsAbstractDatabaseProviderConnection> mConn;

};

void TestQgsQueryResultWidget::initTestCase()
{
QgsApplication::initQgis();
auto conn { QgsProviderRegistry::instance( )->providerMetadata( QStringLiteral( "postgres" ) )->createConnection( qgetenv( "QGIS_PGTEST_DB" ), QVariantMap() ) };
mConn.reset( static_cast<QgsAbstractDatabaseProviderConnection *>( conn ) );
// Prepare data for fetching test
mConn->execSql( QStringLiteral( "DROP TABLE IF EXISTS qgis_test.random_big_data" ) );
mConn->execSql( QStringLiteral( "SELECT * INTO qgis_test.random_big_data FROM generate_series(1,1000000) AS id, md5(random()::text) AS descr" ) );
}

void TestQgsQueryResultWidget::cleanupTestCase()
{
}

void TestQgsQueryResultWidget::init()
{
}

void TestQgsQueryResultWidget::cleanup()
{
}

// Test do not crash when deleting the result while the model fetcher is running
void TestQgsQueryResultWidget::testWidgetCrash()
{
// Make a copy
auto res = new QgsAbstractDatabaseProviderConnection::QueryResult( mConn->execSql( QStringLiteral( "SELECT * FROM qgis_test.random_big_data" ) ) );
auto model = new QgsQueryResultModel( *res );
bool exited { false };
QTimer::singleShot( 0, model, [ & ] { delete res; } );
QTimer::singleShot( 1, model, [ & ] { exited = true; } );
while ( ! exited )
QgsApplication::processEvents();
const auto rowCount { model->rowCount( model->index( -1, -1 ) ) };
QVERIFY( rowCount > 0 && rowCount < 1000000 );
delete model;
}


void TestQgsQueryResultWidget::testWidget()
{
auto d = qgis::make_unique<QDialog>( );
auto l = new QVBoxLayout();
auto w = new QgsQueryResultWidget( d.get(), mConn.release() );
w->setQuery( QStringLiteral( "SELECT * FROM qgis_test.random_big_data" ) );
l->addWidget( w );
d->setLayout( l );
// For interactive testing
//d->exec();
w->executeQuery();
QTimer::singleShot( 0, d.get(), [ = ]
{
QTest::mousePress( w->mStopButton, Qt::MouseButton::LeftButton );
} );
QgsApplication::processEvents();
const auto rowCount { w->mModel->rowCount( w->mModel->index( -1, -1 ) ) };
QVERIFY( rowCount > 0 && rowCount < 1000000 );
}

QGSTEST_MAIN( TestQgsQueryResultWidget )
#include "testqgsqueryresultwidget.moc"
2 changes: 2 additions & 0 deletions tests/src/python/CMakeLists.txt
Expand Up @@ -248,6 +248,8 @@ ADD_PYTHON_TEST(PyQgsProviderSourceWidgetProviderRegistry test_qgssourcewidgetpr
ADD_PYTHON_TEST(PyQgsProviderSublayerDetails test_qgsprovidersublayerdetails.py)
ADD_PYTHON_TEST(PyQgsProviderSublayerModel test_qgsprovidersublayermodel.py)
ADD_PYTHON_TEST(TestQgsRandomMarkerSymbolLayer test_qgsrandommarkersymbollayer.py)
ADD_PYTHON_TEST(PyQgsQueryResultModel test_qgsqueryresultmodel.py)
ADD_PYTHON_TEST(PyQgsQueryResultWidget test_qgsqueryresultwidget.py)
ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py)
ADD_PYTHON_TEST(PyQgsRangeSlider test_qgsrangeslider.py)
ADD_PYTHON_TEST(PyQgsRangeWidgets test_qgsrangewidgets.py)
Expand Down
80 changes: 80 additions & 0 deletions tests/src/python/test_qgsqueryresultwidget.py
@@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsQueryResultWidget.
.. note:: 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.
"""
__author__ = 'Alessandro Pasotti'
__date__ = '2021-01'
__copyright__ = 'Copyright 2021, The QGIS Project'

import os

import qgis # NOQA
from qgis.core import QgsProviderRegistry, QgsQueryResultModel
from qgis.gui import QgsQueryResultWidget
from qgis.PyQt.QtCore import QCoreApplication
from qgis.PyQt.QtWidgets import QDialog, QLabel, QListView, QVBoxLayout
from qgis.testing import start_app, unittest
from utilities import getTestFont

start_app()


class PyQgsQueryResultWidget(unittest.TestCase):

NUM_RECORDS = 1000000

@classmethod
def setUpClass(cls):
"""Run before all tests"""

QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(cls.__name__)
QCoreApplication.setApplicationName(cls.__name__)
start_app()
cls.postgres_conn = "service='qgis_test'"
if 'QGIS_PGTEST_DB' in os.environ:
cls.postgres_conn = os.environ['QGIS_PGTEST_DB']
cls.uri = cls.postgres_conn + ' sslmode=disable'

# Prepare data for threaded test
cls._deleteBigData()
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(cls.uri, {})
conn.executeSql('DROP TABLE IF EXISTS qgis_test.random_big_data')
conn.executeSql('SELECT * INTO qgis_test.random_big_data FROM generate_series(1,%s) AS id, md5(random()::text) AS descr' % cls.NUM_RECORDS)

@classmethod
def tearDownClass(cls):

cls._deleteBigData()

@classmethod
def _deleteBigData(cls):

try:
md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(cls.uri, {})
conn.dropVectorTable('qgis_test', 'random_big_data')
except:
pass

def test_widget(self):

md = QgsProviderRegistry.instance().providerMetadata('postgres')
conn = md.createConnection(self.uri, {})
widget = QgsQueryResultWidget(None, conn)
widget.setQuery("SELECT * FROM qgis_test.random_big_data")
d = QDialog()
l = QVBoxLayout(d)
d.setLayout(l)
l.addWidget(widget)

d.exec_()


if __name__ == '__main__':
unittest.main()

0 comments on commit d914a50

Please sign in to comment.