mirror of
https://github.com/qgis/QGIS.git
synced 2025-06-20 00:03:07 -04:00
Merge pull request #4624 from nyalldawson/feature_source
QgsFeatureSource
This commit is contained in:
commit
9cfe70c058
@ -56,6 +56,7 @@
|
||||
%Include qgsfeatureiterator.sip
|
||||
%Include qgsfeaturerequest.sip
|
||||
%Include qgsfeaturesink.sip
|
||||
%Include qgsfeaturesource.sip
|
||||
%Include qgsfeedback.sip
|
||||
%Include qgsfield.sip
|
||||
%Include qgsfieldconstraints.sip
|
||||
|
81
python/core/qgsfeaturesource.sip
Normal file
81
python/core/qgsfeaturesource.sip
Normal file
@ -0,0 +1,81 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsfeaturesource.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsFeatureSource
|
||||
{
|
||||
%Docstring
|
||||
An interface for objects which provide features via a getFeatures method.
|
||||
|
||||
.. versionadded:: 3.0
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgsfeaturesource.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
virtual ~QgsFeatureSource();
|
||||
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const = 0;
|
||||
%Docstring
|
||||
Returns an iterator for the features in the source.
|
||||
An optional ``request`` can be used to optimise the returned
|
||||
iterator, eg by restricting the returned attributes or geometry.
|
||||
:rtype: QgsFeatureIterator
|
||||
%End
|
||||
|
||||
virtual QgsCoordinateReferenceSystem sourceCrs() const = 0;
|
||||
%Docstring
|
||||
Returns the coordinate reference system for features in the source.
|
||||
:rtype: QgsCoordinateReferenceSystem
|
||||
%End
|
||||
|
||||
virtual QgsFields fields() const = 0;
|
||||
%Docstring
|
||||
Returns the fields associated with features in the source.
|
||||
:rtype: QgsFields
|
||||
%End
|
||||
|
||||
virtual QgsWkbTypes::Type wkbType() const = 0;
|
||||
%Docstring
|
||||
Returns the geometry type for features returned by this source.
|
||||
:rtype: QgsWkbTypes.Type
|
||||
%End
|
||||
|
||||
|
||||
int __len__() const;
|
||||
%Docstring
|
||||
Returns the number of features contained in the source, or -1
|
||||
if the feature count is unknown.
|
||||
:rtype: int
|
||||
%End
|
||||
%MethodCode
|
||||
sipRes = sipCpp->featureCount();
|
||||
%End
|
||||
|
||||
virtual long featureCount() const = 0;
|
||||
%Docstring
|
||||
Returns the number of features contained in the source, or -1
|
||||
if the feature count is unknown.
|
||||
:rtype: long
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/core/qgsfeaturesource.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
@ -13,7 +13,7 @@ typedef QHash<int, QString> QgsAttrPalIndexNameHash;
|
||||
|
||||
|
||||
|
||||
class QgsVectorDataProvider : QgsDataProvider, QgsFeatureSink
|
||||
class QgsVectorDataProvider : QgsDataProvider, QgsFeatureSink, QgsFeatureSource
|
||||
{
|
||||
%Docstring
|
||||
This is the base class for vector data providers.
|
||||
@ -124,6 +124,9 @@ Bitmask of all provider's editing capabilities
|
||||
:rtype: QgsFields
|
||||
%End
|
||||
|
||||
virtual QgsCoordinateReferenceSystem sourceCrs() const;
|
||||
|
||||
|
||||
virtual QString dataComment() const;
|
||||
%Docstring
|
||||
Return a short comment for the data that this provider is
|
||||
|
@ -17,7 +17,7 @@ typedef QList<int> QgsAttributeList;
|
||||
typedef QSet<int> QgsAttributeIds;
|
||||
|
||||
|
||||
class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator, QgsFeatureSink
|
||||
class QgsVectorLayer : QgsMapLayer, QgsExpressionContextGenerator, QgsFeatureSink, QgsFeatureSource
|
||||
{
|
||||
%Docstring
|
||||
Represents a vector layer which manages a vector based data sets.
|
||||
@ -685,7 +685,8 @@ Returns true if this is a geometry layer and false in case of NoGeometry (table
|
||||
:rtype: bool
|
||||
%End
|
||||
|
||||
QgsWkbTypes::Type wkbType() const;
|
||||
virtual QgsWkbTypes::Type wkbType() const;
|
||||
|
||||
%Docstring
|
||||
Returns the WKBType or WKBUnknown in case of error
|
||||
:rtype: QgsWkbTypes.Type
|
||||
@ -697,6 +698,9 @@ Return the provider type for this layer
|
||||
:rtype: str
|
||||
%End
|
||||
|
||||
virtual QgsCoordinateReferenceSystem sourceCrs() const;
|
||||
|
||||
|
||||
virtual bool readXml( const QDomNode &layer_node, const QgsReadWriteContext &context );
|
||||
%Docstring
|
||||
Reads vector layer specific state from project file Dom node.
|
||||
@ -890,7 +894,8 @@ Return the provider type for this layer
|
||||
:rtype: str
|
||||
%End
|
||||
|
||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const;
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const;
|
||||
|
||||
%Docstring
|
||||
Query the layer for features specified in request.
|
||||
\param request feature request describing parameters of features to return
|
||||
@ -1103,7 +1108,7 @@ Return the extent of the layer
|
||||
:rtype: QgsRectangle
|
||||
%End
|
||||
|
||||
QgsFields fields() const;
|
||||
virtual QgsFields fields() const;
|
||||
%Docstring
|
||||
Returns the list of fields of this layer.
|
||||
This also includes fields which have not yet been saved to the provider.
|
||||
@ -1156,7 +1161,8 @@ Returns list of attributes making up the primary key
|
||||
:rtype: long
|
||||
%End
|
||||
|
||||
long featureCount() const;
|
||||
virtual long featureCount() const;
|
||||
|
||||
%Docstring
|
||||
Returns feature count including changes which have not yet been committed
|
||||
If you need only the count of committed features call this method on this layer's provider.
|
||||
|
@ -753,6 +753,7 @@ SET(QGIS_CORE_HDRS
|
||||
qgsfeatureiterator.h
|
||||
qgsfeaturerequest.h
|
||||
qgsfeaturesink.h
|
||||
qgsfeaturesource.h
|
||||
qgsfeaturestore.h
|
||||
qgsfieldformatter.h
|
||||
qgsfield_p.h
|
||||
|
@ -94,5 +94,6 @@ class CORE_EXPORT QgsProxyFeatureSink : public QgsFeatureSink
|
||||
QgsFeatureSink *mSink;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QgsFeatureSink * )
|
||||
|
||||
#endif // QGSFEATURESINK_H
|
||||
|
86
src/core/qgsfeaturesource.h
Executable file
86
src/core/qgsfeaturesource.h
Executable file
@ -0,0 +1,86 @@
|
||||
/***************************************************************************
|
||||
qgsfeaturesource.h
|
||||
----------------
|
||||
begin : May 2017
|
||||
copyright : (C) 2017 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 QGSFEATURESOURCE_H
|
||||
#define QGSFEATURESOURCE_H
|
||||
|
||||
#include "qgis_core.h"
|
||||
#include "qgis.h"
|
||||
|
||||
class QgsFeatureIterator;
|
||||
class QgsFeatureRequest;
|
||||
class QgsCoordinateReferenceSystem;
|
||||
class QgsFields;
|
||||
|
||||
/**
|
||||
* \class QgsFeatureSource
|
||||
* \ingroup core
|
||||
* An interface for objects which provide features via a getFeatures method.
|
||||
*
|
||||
* \since QGIS 3.0
|
||||
*/
|
||||
class CORE_EXPORT QgsFeatureSource
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~QgsFeatureSource() = default;
|
||||
|
||||
/**
|
||||
* Returns an iterator for the features in the source.
|
||||
* An optional \a request can be used to optimise the returned
|
||||
* iterator, eg by restricting the returned attributes or geometry.
|
||||
*/
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const = 0;
|
||||
|
||||
/**
|
||||
* Returns the coordinate reference system for features in the source.
|
||||
*/
|
||||
virtual QgsCoordinateReferenceSystem sourceCrs() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the fields associated with features in the source.
|
||||
*/
|
||||
virtual QgsFields fields() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the geometry type for features returned by this source.
|
||||
*/
|
||||
virtual QgsWkbTypes::Type wkbType() const = 0;
|
||||
|
||||
#ifdef SIP_RUN
|
||||
|
||||
/**
|
||||
* Returns the number of features contained in the source, or -1
|
||||
* if the feature count is unknown.
|
||||
*/
|
||||
int __len__() const;
|
||||
% MethodCode
|
||||
sipRes = sipCpp->featureCount();
|
||||
% End
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Returns the number of features contained in the source, or -1
|
||||
* if the feature count is unknown.
|
||||
*/
|
||||
virtual long featureCount() const = 0;
|
||||
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE( QgsFeatureSource * )
|
||||
|
||||
#endif // QGSFEATURESOURCE_H
|
@ -47,6 +47,11 @@ QString QgsVectorDataProvider::storageType() const
|
||||
return QStringLiteral( "Generic vector file" );
|
||||
}
|
||||
|
||||
QgsCoordinateReferenceSystem QgsVectorDataProvider::sourceCrs() const
|
||||
{
|
||||
return crs();
|
||||
}
|
||||
|
||||
QString QgsVectorDataProvider::dataComment() const
|
||||
{
|
||||
return QString();
|
||||
|
@ -31,6 +31,7 @@ class QTextCodec;
|
||||
#include "qgsmaplayerdependency.h"
|
||||
#include "qgsrelation.h"
|
||||
#include "qgsfeaturesink.h"
|
||||
#include "qgsfeaturesource.h"
|
||||
|
||||
typedef QList<int> QgsAttributeList SIP_SKIP;
|
||||
typedef QSet<int> QgsAttributeIds SIP_SKIP;
|
||||
@ -50,7 +51,7 @@ class QgsFeedback;
|
||||
*
|
||||
*
|
||||
*/
|
||||
class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeatureSink
|
||||
class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeatureSink, public QgsFeatureSource
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -139,23 +140,25 @@ class CORE_EXPORT QgsVectorDataProvider : public QgsDataProvider, public QgsFeat
|
||||
* \param request feature request describing parameters of features to return
|
||||
* \returns iterator for matching features from provider
|
||||
*/
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const = 0;
|
||||
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const override = 0;
|
||||
|
||||
/**
|
||||
* Returns the geometry type which is returned by this layer
|
||||
*/
|
||||
virtual QgsWkbTypes::Type wkbType() const = 0;
|
||||
virtual QgsWkbTypes::Type wkbType() const override = 0;
|
||||
|
||||
/**
|
||||
* Number of features in the layer
|
||||
* \returns long containing number of features
|
||||
*/
|
||||
virtual long featureCount() const = 0;
|
||||
virtual long featureCount() const override = 0;
|
||||
|
||||
/**
|
||||
* Returns the fields associated with this data provider.
|
||||
*/
|
||||
virtual QgsFields fields() const = 0;
|
||||
virtual QgsFields fields() const override = 0;
|
||||
|
||||
QgsCoordinateReferenceSystem sourceCrs() const override;
|
||||
|
||||
/**
|
||||
* Return a short comment for the data that this provider is
|
||||
|
@ -309,6 +309,11 @@ QString QgsVectorLayer::providerType() const
|
||||
return mProviderKey;
|
||||
}
|
||||
|
||||
QgsCoordinateReferenceSystem QgsVectorLayer::sourceCrs() const
|
||||
{
|
||||
return crs();
|
||||
}
|
||||
|
||||
void QgsVectorLayer::reload()
|
||||
{
|
||||
if ( mDataProvider )
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "qgsmaplayer.h"
|
||||
#include "qgsfeature.h"
|
||||
#include "qgsfeaturerequest.h"
|
||||
#include "qgsfeaturesource.h"
|
||||
#include "qgsfields.h"
|
||||
#include "qgsvectordataprovider.h"
|
||||
#include "qgsvectorsimplifymethod.h"
|
||||
@ -346,7 +347,7 @@ typedef QSet<int> QgsAttributeIds;
|
||||
* TODO QGIS3: Remove virtual from non-inherited methods (like isModified)
|
||||
* \see QgsVectorLayerUtils()
|
||||
*/
|
||||
class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionContextGenerator, public QgsFeatureSink
|
||||
class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionContextGenerator, public QgsFeatureSink, public QgsFeatureSource
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
@ -690,11 +691,13 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
bool hasGeometryType() const;
|
||||
|
||||
//! Returns the WKBType or WKBUnknown in case of error
|
||||
QgsWkbTypes::Type wkbType() const;
|
||||
QgsWkbTypes::Type wkbType() const override;
|
||||
|
||||
//! Return the provider type for this layer
|
||||
QString providerType() const;
|
||||
|
||||
QgsCoordinateReferenceSystem sourceCrs() const override;
|
||||
|
||||
/** Reads vector layer specific state from project file Dom node.
|
||||
* \note Called by QgsMapLayer::readXml().
|
||||
*/
|
||||
@ -859,7 +862,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
* \param request feature request describing parameters of features to return
|
||||
* \returns iterator for matching features from provider
|
||||
*/
|
||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const;
|
||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() ) const override;
|
||||
|
||||
/**
|
||||
* Query the layer for features matching a given expression.
|
||||
@ -1082,7 +1085,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
*
|
||||
* \returns A list of fields
|
||||
*/
|
||||
inline QgsFields fields() const { return mFields; }
|
||||
inline QgsFields fields() const override { return mFields; }
|
||||
|
||||
/**
|
||||
* Returns the list of fields of this layer.
|
||||
@ -1124,7 +1127,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
|
||||
* Returns feature count including changes which have not yet been committed
|
||||
* If you need only the count of committed features call this method on this layer's provider.
|
||||
*/
|
||||
long featureCount() const;
|
||||
long featureCount() const override;
|
||||
|
||||
/** Make layer read-only (editing disabled) or not
|
||||
* \returns false if the layer is in editing yet
|
||||
|
@ -142,6 +142,18 @@ QgsDelimitedTextFeatureIterator::QgsDelimitedTextFeatureIterator( QgsDelimitedTe
|
||||
attributeIndexes += attrs.toSet();
|
||||
mRequest.setSubsetOfAttributes( attributeIndexes.toList() );
|
||||
}
|
||||
// also need attributes required by order by
|
||||
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
|
||||
{
|
||||
QgsAttributeList attrs = request.subsetOfAttributes();
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
int attrIndex = mSource->mFields.lookupField( attr );
|
||||
if ( !attrs.contains( attrIndex ) )
|
||||
attrs << attrIndex;
|
||||
}
|
||||
mRequest.setSubsetOfAttributes( attrs );
|
||||
}
|
||||
|
||||
QgsDebugMsg( QString( "Iterator is scanning file: " ) + ( mMode == FileScan ? "Yes" : "No" ) );
|
||||
QgsDebugMsg( QString( "Iterator is loading geometries: " ) + ( mLoadGeometry ? "Yes" : "No" ) );
|
||||
|
@ -87,6 +87,19 @@ QgsOgrFeatureIterator::QgsOgrFeatureIterator( QgsOgrFeatureSource *source, bool
|
||||
attrs = attributeIndexes.toList();
|
||||
mRequest.setSubsetOfAttributes( attrs );
|
||||
}
|
||||
// also need attributes required by order by
|
||||
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
|
||||
{
|
||||
QSet<int> attributeIndexes;
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
attributeIndexes << mSource->mFields.lookupField( attr );
|
||||
}
|
||||
attributeIndexes += attrs.toSet();
|
||||
attrs = attributeIndexes.toList();
|
||||
mRequest.setSubsetOfAttributes( attrs );
|
||||
}
|
||||
|
||||
if ( request.filterType() == QgsFeatureRequest::FilterExpression && request.filterExpression()->needsGeometry() )
|
||||
{
|
||||
mFetchGeometry = true;
|
||||
|
@ -163,6 +163,19 @@ QgsPostgresFeatureIterator::QgsPostgresFeatureIterator( QgsPostgresFeatureSource
|
||||
mOrderByCompiled = false;
|
||||
}
|
||||
|
||||
// ensure that all attributes required for order by are fetched
|
||||
if ( !mOrderByCompiled && mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes )
|
||||
{
|
||||
QgsAttributeList attrs = mRequest.subsetOfAttributes();
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
int attrIndex = mSource->mFields.lookupField( attr );
|
||||
if ( !attrs.contains( attrIndex ) )
|
||||
attrs << attrIndex;
|
||||
}
|
||||
mRequest.setSubsetOfAttributes( attrs );
|
||||
}
|
||||
|
||||
if ( !mOrderByCompiled )
|
||||
limitAtProvider = false;
|
||||
|
||||
|
@ -129,7 +129,6 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
whereClause = whereClauses.join( QStringLiteral( " AND " ) );
|
||||
|
||||
// Setup the order by
|
||||
@ -175,6 +174,18 @@ QgsSpatiaLiteFeatureIterator::QgsSpatiaLiteFeatureIterator( QgsSpatiaLiteFeature
|
||||
if ( !mOrderByCompiled )
|
||||
limitAtProvider = false;
|
||||
|
||||
// also need attributes required by order by
|
||||
if ( !mOrderByCompiled && mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
|
||||
{
|
||||
QSet<int> attributeIndexes;
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
attributeIndexes << mSource->mFields.lookupField( attr );
|
||||
}
|
||||
attributeIndexes += mRequest.subsetOfAttributes().toSet();
|
||||
mRequest.setSubsetOfAttributes( attributeIndexes.toList() );
|
||||
}
|
||||
|
||||
// preparing the SQL statement
|
||||
bool success = prepareStatement( whereClause, limitAtProvider ? mRequest.limit() : -1, orderByParts.join( QStringLiteral( "," ) ) );
|
||||
if ( !success && useFallbackWhereClause )
|
||||
|
@ -109,6 +109,17 @@ QgsVirtualLayerFeatureIterator::QgsVirtualLayerFeatureIterator( QgsVirtualLayerF
|
||||
mAttributes << attrIdx;
|
||||
}
|
||||
}
|
||||
|
||||
// also need attributes required by order by
|
||||
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
|
||||
{
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
int attrIdx = mSource->mFields.lookupField( attr );
|
||||
if ( !mAttributes.contains( attrIdx ) )
|
||||
mAttributes << attrIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -854,6 +854,21 @@ QgsWFSFeatureIterator::QgsWFSFeatureIterator( QgsWFSFeatureSource *source,
|
||||
}
|
||||
}
|
||||
|
||||
// also need attributes required by order by
|
||||
if ( mRequest.flags() & QgsFeatureRequest::SubsetOfAttributes && !mRequest.orderBy().isEmpty() )
|
||||
{
|
||||
Q_FOREACH ( const QString &attr, mRequest.orderBy().usedAttributes() )
|
||||
{
|
||||
int idx = dataProviderFields.indexFromName( attr );
|
||||
if ( idx >= 0 && !cacheSubSet.contains( idx ) )
|
||||
cacheSubSet.append( idx );
|
||||
|
||||
idx = mShared->mFields.indexFromName( attr );
|
||||
if ( idx >= 0 && !mSubSetAttributes.contains( idx ) )
|
||||
mSubSetAttributes.append( idx );
|
||||
}
|
||||
}
|
||||
|
||||
if ( mFetchGeometry )
|
||||
{
|
||||
int hexwkbGeomIdx = dataProviderFields.indexFromName( QgsWFSConstants::FIELD_HEXWKB_GEOM );
|
||||
|
605
tests/src/python/featuresourcetestbase.py
Executable file
605
tests/src/python/featuresourcetestbase.py
Executable file
@ -0,0 +1,605 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""QGIS Unit test utils for QgsFeatureSource subclasses.
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
|
||||
from builtins import str
|
||||
from builtins import object
|
||||
__author__ = 'Nyall Dawson'
|
||||
__date__ = '2017-05-25'
|
||||
__copyright__ = 'Copyright 2017, The QGIS Project'
|
||||
# This will get replaced with a git SHA1 when you do a git archive
|
||||
__revision__ = '$Format:%H$'
|
||||
|
||||
from qgis.core import (
|
||||
QgsRectangle,
|
||||
QgsFeatureRequest,
|
||||
QgsFeature,
|
||||
QgsWkbTypes,
|
||||
QgsGeometry,
|
||||
QgsAbstractFeatureIterator,
|
||||
QgsExpressionContextScope,
|
||||
QgsExpressionContext,
|
||||
QgsVectorLayerFeatureSource,
|
||||
NULL
|
||||
)
|
||||
|
||||
from utilities import compareWkt
|
||||
|
||||
|
||||
class FeatureSourceTestCase(object):
|
||||
|
||||
'''
|
||||
This is a collection of tests for QgsFeatureSources subclasses and kept generic.
|
||||
To make use of it, subclass it and set self.source to a QgsFeatureSource you want to test.
|
||||
Make sure that your source uses the default dataset by converting one of the provided datasets from the folder
|
||||
tests/testdata/source to a dataset your source is able to handle.
|
||||
|
||||
'''
|
||||
|
||||
def testCrs(self):
|
||||
self.assertEqual(self.source.sourceCrs().authid(), 'EPSG:4326')
|
||||
|
||||
def testWkbType(self):
|
||||
self.assertEqual(self.source.wkbType(), QgsWkbTypes.Point)
|
||||
|
||||
def testFeatureCount(self):
|
||||
self.assertEqual(self.source.featureCount(), 5)
|
||||
self.assertEqual(len(self.source), 5)
|
||||
|
||||
def testFields(self):
|
||||
fields = self.source.fields()
|
||||
for f in ('pk', 'cnt', 'name', 'name2', 'num_char'):
|
||||
self.assertTrue(fields.lookupField(f) >= 0)
|
||||
|
||||
def testGetFeatures(self, source=None, extra_features=[], skip_features=[], changed_attributes={}, changed_geometries={}):
|
||||
""" Test that expected results are returned when fetching all features """
|
||||
|
||||
# IMPORTANT - we do not use `for f in source.getFeatures()` as we are also
|
||||
# testing that existing attributes & geometry in f are overwritten correctly
|
||||
# (for f in ... uses a new QgsFeature for every iteration)
|
||||
|
||||
if not source:
|
||||
source = self.source
|
||||
|
||||
it = source.getFeatures()
|
||||
f = QgsFeature()
|
||||
attributes = {}
|
||||
geometries = {}
|
||||
while it.nextFeature(f):
|
||||
# expect feature to be valid
|
||||
self.assertTrue(f.isValid())
|
||||
# split off the first 5 attributes only - some source test datasets will include
|
||||
# additional attributes which we ignore
|
||||
attrs = f.attributes()[0:5]
|
||||
# force the num_char attribute to be text - some sources (e.g., delimited text) will
|
||||
# automatically detect that this attribute contains numbers and set it as a numeric
|
||||
# field
|
||||
attrs[4] = str(attrs[4])
|
||||
attributes[f['pk']] = attrs
|
||||
geometries[f['pk']] = f.hasGeometry() and f.geometry().exportToWkt()
|
||||
|
||||
expected_attributes = {5: [5, -200, NULL, 'NuLl', '5'],
|
||||
3: [3, 300, 'Pear', 'PEaR', '3'],
|
||||
1: [1, 100, 'Orange', 'oranGe', '1'],
|
||||
2: [2, 200, 'Apple', 'Apple', '2'],
|
||||
4: [4, 400, 'Honey', 'Honey', '4']}
|
||||
|
||||
expected_geometries = {1: 'Point (-70.332 66.33)',
|
||||
2: 'Point (-68.2 70.8)',
|
||||
3: None,
|
||||
4: 'Point(-65.32 78.3)',
|
||||
5: 'Point(-71.123 78.23)'}
|
||||
for f in extra_features:
|
||||
expected_attributes[f[0]] = f.attributes()
|
||||
if f.hasGeometry():
|
||||
expected_geometries[f[0]] = f.geometry().exportToWkt()
|
||||
else:
|
||||
expected_geometries[f[0]] = None
|
||||
|
||||
for i in skip_features:
|
||||
del expected_attributes[i]
|
||||
del expected_geometries[i]
|
||||
for i, a in changed_attributes.items():
|
||||
for attr_idx, v in a.items():
|
||||
expected_attributes[i][attr_idx] = v
|
||||
for i, g, in changed_geometries.items():
|
||||
if g:
|
||||
expected_geometries[i] = g.exportToWkt()
|
||||
else:
|
||||
expected_geometries[i] = None
|
||||
|
||||
self.assertEqual(attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes))
|
||||
|
||||
self.assertEqual(len(expected_geometries), len(geometries))
|
||||
|
||||
for pk, geom in list(expected_geometries.items()):
|
||||
if geom:
|
||||
assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format(pk, geom, geometries[pk])
|
||||
else:
|
||||
self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
|
||||
|
||||
def assert_query(self, source, expression, expected):
|
||||
request = QgsFeatureRequest().setFilterExpression(expression).setFlags(QgsFeatureRequest.NoGeometry)
|
||||
result = set([f['pk'] for f in source.getFeatures(request)])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}"'.format(set(expected), result, expression)
|
||||
self.assertTrue(all(f.isValid() for f in source.getFeatures(request)))
|
||||
|
||||
# Also check that filter works when referenced fields are not being retrieved by request
|
||||
result = set([f['pk'] for f in source.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setSubsetOfAttributes([0]))])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}" using empty attribute subset'.format(set(expected), result, expression)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
request = QgsFeatureRequest().setFilterExpression(expression)
|
||||
for f in source.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)
|
||||
|
||||
def runGetFeatureTests(self, source):
|
||||
assert len([f for f in source.getFeatures()]) == 5
|
||||
self.assert_query(source, 'name ILIKE \'QGIS\'', [])
|
||||
self.assert_query(source, '"name" IS NULL', [5])
|
||||
self.assert_query(source, '"name" IS NOT NULL', [1, 2, 3, 4])
|
||||
self.assert_query(source, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
|
||||
self.assert_query(source, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
|
||||
self.assert_query(source, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
|
||||
self.assert_query(source, 'name = \'Apple\'', [2])
|
||||
self.assert_query(source, 'name <> \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(source, 'name = \'apple\'', [])
|
||||
self.assert_query(source, '"name" <> \'apple\'', [1, 2, 3, 4])
|
||||
self.assert_query(source, '(name = \'Apple\') is not null', [1, 2, 3, 4])
|
||||
self.assert_query(source, 'name LIKE \'Apple\'', [2])
|
||||
self.assert_query(source, 'name LIKE \'aPple\'', [])
|
||||
self.assert_query(source, 'name ILIKE \'aPple\'', [2])
|
||||
self.assert_query(source, 'name ILIKE \'%pp%\'', [2])
|
||||
self.assert_query(source, 'cnt > 0', [1, 2, 3, 4])
|
||||
self.assert_query(source, '-cnt > 0', [5])
|
||||
self.assert_query(source, 'cnt < 0', [5])
|
||||
self.assert_query(source, '-cnt < 0', [1, 2, 3, 4])
|
||||
self.assert_query(source, 'cnt >= 100', [1, 2, 3, 4])
|
||||
self.assert_query(source, 'cnt <= 100', [1, 5])
|
||||
self.assert_query(source, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
|
||||
self.assert_query(source, 'cnt = 50 * 2', [1])
|
||||
self.assert_query(source, 'cnt = 150 / 1.5', [1])
|
||||
self.assert_query(source, 'cnt = 1000 / 10', [1])
|
||||
self.assert_query(source, 'cnt = 1000/11+10', []) # checks that source isn't rounding int/int
|
||||
self.assert_query(source, 'pk = 9 // 4', [2]) # int division
|
||||
self.assert_query(source, 'cnt = 99 + 1', [1])
|
||||
self.assert_query(source, 'cnt = 101 - 1', [1])
|
||||
self.assert_query(source, 'cnt - 1 = 99', [1])
|
||||
self.assert_query(source, '-cnt - 1 = -101', [1])
|
||||
self.assert_query(source, '-(-cnt) = 100', [1])
|
||||
self.assert_query(source, '-(cnt) = -(100)', [1])
|
||||
self.assert_query(source, 'cnt + 1 = 101', [1])
|
||||
self.assert_query(source, 'cnt = 1100 % 1000', [1])
|
||||
self.assert_query(source, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
|
||||
self.assert_query(source, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
|
||||
self.assert_query(source, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
|
||||
self.assert_query(source, '\'x\' || "name" IS NULL', [5])
|
||||
self.assert_query(source, 'cnt = 10 ^ 2', [1])
|
||||
self.assert_query(source, '"name" ~ \'[OP]ra[gne]+\'', [1])
|
||||
self.assert_query(source, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names
|
||||
self.assert_query(source, 'true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'false', [])
|
||||
|
||||
# Three value logic
|
||||
self.assert_query(source, 'false and false', [])
|
||||
self.assert_query(source, 'false and true', [])
|
||||
self.assert_query(source, 'false and NULL', [])
|
||||
self.assert_query(source, 'true and false', [])
|
||||
self.assert_query(source, 'true and true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'true and NULL', [])
|
||||
self.assert_query(source, 'NULL and false', [])
|
||||
self.assert_query(source, 'NULL and true', [])
|
||||
self.assert_query(source, 'NULL and NULL', [])
|
||||
self.assert_query(source, 'false or false', [])
|
||||
self.assert_query(source, 'false or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'false or NULL', [])
|
||||
self.assert_query(source, 'true or false', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'true or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'true or NULL', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'NULL or false', [])
|
||||
self.assert_query(source, 'NULL or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'NULL or NULL', [])
|
||||
self.assert_query(source, 'not true', [])
|
||||
self.assert_query(source, 'not false', [1, 2, 3, 4, 5])
|
||||
self.assert_query(source, 'not null', [])
|
||||
|
||||
# not
|
||||
self.assert_query(source, 'not name = \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(source, 'not name IS NULL', [1, 2, 3, 4])
|
||||
self.assert_query(source, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
|
||||
self.assert_query(source, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(source, 'not name = \'Apple\' and pk = 4', [4])
|
||||
self.assert_query(source, 'not name = \'Apple\' and not pk = 4', [1, 3])
|
||||
self.assert_query(source, 'not pk IN (1, 2, 4, 8)', [3, 5])
|
||||
|
||||
# type conversion - QGIS expressions do not mind that we are comparing a string
|
||||
# against numeric literals
|
||||
self.assert_query(source, 'num_char IN (2, 4, 5)', [2, 4, 5])
|
||||
|
||||
#function
|
||||
self.assert_query(source, 'sqrt(pk) >= 2', [4, 5])
|
||||
self.assert_query(source, 'radians(cnt) < 2', [1, 5])
|
||||
self.assert_query(source, 'degrees(pk) <= 200', [1, 2, 3])
|
||||
self.assert_query(source, 'abs(cnt) <= 200', [1, 2, 5])
|
||||
self.assert_query(source, 'cos(pk) < 0', [2, 3, 4])
|
||||
self.assert_query(source, 'sin(pk) < 0', [4, 5])
|
||||
self.assert_query(source, 'tan(pk) < 0', [2, 3, 5])
|
||||
self.assert_query(source, 'acos(-1) < pk', [4, 5])
|
||||
self.assert_query(source, 'asin(1) < pk', [2, 3, 4, 5])
|
||||
self.assert_query(source, 'atan(3.14) < pk', [2, 3, 4, 5])
|
||||
self.assert_query(source, 'atan2(3.14, pk) < 1', [3, 4, 5])
|
||||
self.assert_query(source, 'exp(pk) < 10', [1, 2])
|
||||
self.assert_query(source, 'ln(pk) <= 1', [1, 2])
|
||||
self.assert_query(source, 'log(3, pk) <= 1', [1, 2, 3])
|
||||
self.assert_query(source, 'log10(pk) < 0.5', [1, 2, 3])
|
||||
self.assert_query(source, 'round(3.14) <= pk', [3, 4, 5])
|
||||
self.assert_query(source, 'round(0.314,1) * 10 = pk', [3])
|
||||
self.assert_query(source, 'floor(3.14) <= pk', [3, 4, 5])
|
||||
self.assert_query(source, 'ceil(3.14) <= pk', [4, 5])
|
||||
self.assert_query(source, 'pk < pi()', [1, 2, 3])
|
||||
|
||||
self.assert_query(source, 'round(cnt / 66.67) <= 2', [1, 5])
|
||||
self.assert_query(source, 'floor(cnt / 66.67) <= 2', [1, 2, 5])
|
||||
self.assert_query(source, 'ceil(cnt / 66.67) <= 2', [1, 5])
|
||||
self.assert_query(source, 'pk < pi() / 2', [1])
|
||||
self.assert_query(source, 'pk = char(51)', [3])
|
||||
self.assert_query(source, 'pk = coalesce(NULL,3,4)', [3])
|
||||
self.assert_query(source, 'lower(name) = \'apple\'', [2])
|
||||
self.assert_query(source, 'upper(name) = \'APPLE\'', [2])
|
||||
self.assert_query(source, 'name = trim(\' Apple \')', [2])
|
||||
|
||||
# geometry
|
||||
# azimuth and touches tests are deactivated because they do not pass for WFS source
|
||||
#self.assert_query(source, 'azimuth($geometry,geom_from_wkt( \'Point (-70 70)\')) < pi()', [1, 5])
|
||||
self.assert_query(source, 'x($geometry) < -70', [1, 5])
|
||||
self.assert_query(source, 'y($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(source, 'xmin($geometry) < -70', [1, 5])
|
||||
self.assert_query(source, 'ymin($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(source, 'xmax($geometry) < -70', [1, 5])
|
||||
self.assert_query(source, 'ymax($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(source, 'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [4, 5])
|
||||
self.assert_query(source, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])
|
||||
#self.assert_query(source, 'touches($geometry,geom_from_wkt( \'Polygon ((-70.332 66.33, -65.32 66.33, -65.32 78.3, -70.332 78.3, -70.332 66.33))\'))', [1, 4])
|
||||
self.assert_query(source, 'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)', [1, 2])
|
||||
self.assert_query(source, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [4, 5])
|
||||
self.assert_query(source, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2])
|
||||
|
||||
# combination of an uncompilable expression and limit
|
||||
|
||||
# TODO - move this test to FeatureSourceTestCase
|
||||
# it's currently added in ProviderTestCase, but tests only using a QgsVectorLayer getting features,
|
||||
# i.e. not directly requesting features from the provider. Turns out the WFS provider fails this
|
||||
# and should be fixed - then we can enable this test at the FeatureSourceTestCase level
|
||||
|
||||
#feature = next(self.source.getFeatures(QgsFeatureRequest().setFilterExpression('pk=4')))
|
||||
#context = QgsExpressionContext()
|
||||
#scope = QgsExpressionContextScope()
|
||||
#scope.setVariable('parent', feature)
|
||||
#context.appendScope(scope)
|
||||
|
||||
#request = QgsFeatureRequest()
|
||||
#request.setExpressionContext(context)
|
||||
#request.setFilterExpression('"pk" = attribute(@parent, \'pk\')')
|
||||
#request.setLimit(1)
|
||||
|
||||
#values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
#self.assertEqual(values, [4])
|
||||
|
||||
def testGetFeaturesExp(self):
|
||||
self.runGetFeatureTests(self.source)
|
||||
|
||||
def runOrderByTests(self):
|
||||
request = QgsFeatureRequest().addOrderBy('cnt')
|
||||
values = [f['cnt'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [-200, 100, 200, 300, 400])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('cnt', False)
|
||||
values = [f['cnt'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [400, 300, 200, 100, -200])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name')
|
||||
values = [f['name'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, ['Apple', 'Honey', 'Orange', 'Pear', NULL])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', True, True)
|
||||
values = [f['name'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [NULL, 'Apple', 'Honey', 'Orange', 'Pear'])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', False)
|
||||
values = [f['name'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [NULL, 'Pear', 'Orange', 'Honey', 'Apple'])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', False, False)
|
||||
values = [f['name'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, ['Pear', 'Orange', 'Honey', 'Apple', NULL])
|
||||
|
||||
# Case sensitivity
|
||||
request = QgsFeatureRequest().addOrderBy('name2')
|
||||
values = [f['name2'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, ['Apple', 'Honey', 'NuLl', 'oranGe', 'PEaR'])
|
||||
|
||||
# Combination with LIMIT
|
||||
request = QgsFeatureRequest().addOrderBy('pk', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
|
||||
# A slightly more complex expression
|
||||
request = QgsFeatureRequest().addOrderBy('pk*2', False)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4, 3, 2, 1])
|
||||
|
||||
# Order reversing expression
|
||||
request = QgsFeatureRequest().addOrderBy('pk*-1', False)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [1, 2, 3, 4, 5])
|
||||
|
||||
# Type dependent expression
|
||||
request = QgsFeatureRequest().addOrderBy('num_char*2', False)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4, 3, 2, 1])
|
||||
|
||||
# Order by guaranteed to fail
|
||||
request = QgsFeatureRequest().addOrderBy('not a valid expression*', False)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(set(values), set([5, 4, 3, 2, 1]))
|
||||
|
||||
# Multiple order bys and boolean
|
||||
request = QgsFeatureRequest().addOrderBy('pk > 2').addOrderBy('pk', False)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [2, 1, 5, 4, 3])
|
||||
|
||||
# Multiple order bys, one bad, and a limit
|
||||
request = QgsFeatureRequest().addOrderBy('pk', False).addOrderBy('not a valid expression*', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
|
||||
# Bad expression first
|
||||
request = QgsFeatureRequest().addOrderBy('not a valid expression*', False).addOrderBy('pk', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
|
||||
# Combination with subset of attributes
|
||||
request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes(['pk'], self.source.fields())
|
||||
values = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4, 3, 2, 1])
|
||||
|
||||
def testOrderBy(self):
|
||||
self.runOrderByTests()
|
||||
|
||||
def testOpenIteratorAfterSourceRemoval(self):
|
||||
"""
|
||||
Test that removing source after opening an iterator does not crash. All required
|
||||
information should be captured in the iterator's source and there MUST be no
|
||||
links between the iterators and the sources's data source
|
||||
"""
|
||||
if not getattr(self, 'getSource', None):
|
||||
return
|
||||
|
||||
source = self.getSource()
|
||||
it = source.getFeatures()
|
||||
del source
|
||||
|
||||
# get the features
|
||||
pks = []
|
||||
for f in it:
|
||||
pks.append(f['pk'])
|
||||
self.assertEqual(set(pks), {1, 2, 3, 4, 5})
|
||||
|
||||
def testGetFeaturesFidTests(self):
|
||||
fids = [f.id() for f in self.source.getFeatures()]
|
||||
assert len(fids) == 5, 'Expected 5 features, got {} instead'.format(len(fids))
|
||||
for id in fids:
|
||||
features = [f for f in self.source.getFeatures(QgsFeatureRequest().setFilterFid(id))]
|
||||
self.assertEqual(len(features), 1)
|
||||
feature = features[0]
|
||||
self.assertTrue(feature.isValid())
|
||||
|
||||
result = [feature.id()]
|
||||
expected = [id]
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature ID filter'.format(expected, result)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
request = QgsFeatureRequest().setFilterFid(id)
|
||||
for f in self.source.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f.id() == id)
|
||||
|
||||
# bad features
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setFilterFid(-99999999))
|
||||
feature = QgsFeature(5)
|
||||
feature.setValid(False)
|
||||
self.assertFalse(it.nextFeature(feature))
|
||||
self.assertFalse(feature.isValid())
|
||||
|
||||
def testGetFeaturesFidsTests(self):
|
||||
fids = [f.id() for f in self.source.getFeatures()]
|
||||
self.assertEqual(len(fids), 5)
|
||||
|
||||
request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]])
|
||||
result = set([f.id() for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
expected = set([fids[0], fids[2]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.source.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f.id() in expected)
|
||||
|
||||
result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))])
|
||||
expected = set([fids[1], fids[3], fids[4]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
#sources should ignore non-existent fids
|
||||
result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([-101, fids[1], -102, fids[3], -103, fids[4], -104]))])
|
||||
expected = set([fids[1], fids[3], fids[4]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
result = set([f.id() for f in self.source.getFeatures(QgsFeatureRequest().setFilterFids([]))])
|
||||
expected = set([])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
# Rewind mid-way
|
||||
request = QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]])
|
||||
feature_it = self.source.getFeatures(request)
|
||||
feature = QgsFeature()
|
||||
feature.setValid(True)
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertIn(feature.id(), [fids[1], fids[3], fids[4]])
|
||||
first_feature = feature
|
||||
self.assertTrue(feature.isValid())
|
||||
# rewind
|
||||
self.assertTrue(feature_it.rewind())
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertEqual(feature.id(), first_feature.id())
|
||||
self.assertTrue(feature.isValid())
|
||||
# grab all features
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
# none left
|
||||
self.assertFalse(feature_it.nextFeature(feature))
|
||||
self.assertFalse(feature.isValid())
|
||||
|
||||
def testGetFeaturesFilterRectTests(self):
|
||||
extent = QgsRectangle(-70, 67, -60, 80)
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
features = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
assert set(features) == set([2, 4]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.source.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in set([2, 4]))
|
||||
|
||||
# test with an empty rectangle
|
||||
extent = QgsRectangle()
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
features = [f['pk'] for f in self.source.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
def testRectAndExpression(self):
|
||||
extent = QgsRectangle(-70, 67, -60, 80)
|
||||
request = QgsFeatureRequest().setFilterExpression('"cnt">200').setFilterRect(extent)
|
||||
result = set([f['pk'] for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
expected = [4]
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(set(expected), result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# shouldn't matter what order this is done in
|
||||
request = QgsFeatureRequest().setFilterRect(extent).setFilterExpression('"cnt">200')
|
||||
result = set([f['pk'] for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
expected = [4]
|
||||
assert set(
|
||||
expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(
|
||||
set(expected), result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.source.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)
|
||||
|
||||
def testGetFeaturesLimit(self):
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setLimit(2))
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features, got {} instead'.format(len(features))
|
||||
# fetch one feature
|
||||
feature = QgsFeature()
|
||||
assert not it.nextFeature(feature), 'Expected no feature after limit, got one'
|
||||
it.rewind()
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features after rewind, got {} instead'.format(len(features))
|
||||
it.rewind()
|
||||
assert it.nextFeature(feature), 'Expected feature after rewind, got none'
|
||||
it.rewind()
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features after rewind, got {} instead'.format(len(features))
|
||||
# test with expression, both with and without compilation
|
||||
try:
|
||||
self.disableCompiler()
|
||||
except AttributeError:
|
||||
pass
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setLimit(2).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
try:
|
||||
self.enableCompiler()
|
||||
except AttributeError:
|
||||
pass
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setLimit(2).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
# limit to more features than exist
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setLimit(3).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
# limit to less features than possible
|
||||
it = self.source.getFeatures(QgsFeatureRequest().setLimit(1).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert 1 in features or 5 in features, 'Expected either 1 or 5 for expression and feature limit, Got {} instead'.format(features)
|
||||
|
||||
def testClosedIterators(self):
|
||||
""" Test behavior of closed iterators """
|
||||
|
||||
# Test retrieving feature after closing iterator
|
||||
f_it = self.source.getFeatures(QgsFeatureRequest())
|
||||
fet = QgsFeature()
|
||||
assert f_it.nextFeature(fet), 'Could not fetch feature'
|
||||
assert fet.isValid(), 'Feature is not valid'
|
||||
assert f_it.close(), 'Could not close iterator'
|
||||
self.assertFalse(f_it.nextFeature(fet), 'Fetched feature after iterator closed, expected nextFeature() to return False')
|
||||
self.assertFalse(fet.isValid(), 'Valid feature fetched from closed iterator, should be invalid')
|
||||
|
||||
# Test rewinding closed iterator
|
||||
self.assertFalse(f_it.rewind(), 'Rewinding closed iterator successful, should not be allowed')
|
||||
|
||||
def testGetFeaturesSubsetAttributes(self):
|
||||
""" Test that expected results are returned when using subsets of attributes """
|
||||
|
||||
tests = {'pk': set([1, 2, 3, 4, 5]),
|
||||
'cnt': set([-200, 300, 100, 200, 400]),
|
||||
'name': set(['Pear', 'Orange', 'Apple', 'Honey', NULL]),
|
||||
'name2': set(['NuLl', 'PEaR', 'oranGe', 'Apple', 'Honey'])}
|
||||
for field, expected in list(tests.items()):
|
||||
request = QgsFeatureRequest().setSubsetOfAttributes([field], self.source.fields())
|
||||
result = set([f[field] for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
self.assertEqual(result, expected, 'Expected {}, got {}'.format(expected, result))
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
def testGetFeaturesSubsetAttributes2(self):
|
||||
""" Test that other fields are NULL when fetching subsets of attributes """
|
||||
|
||||
for field_to_fetch in ['pk', 'cnt', 'name', 'name2']:
|
||||
for f in self.source.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_to_fetch], self.source.fields())):
|
||||
# Check that all other fields are NULL and force name to lower-case
|
||||
for other_field in [field.name() for field in self.source.fields() if field.name().lower() != field_to_fetch]:
|
||||
if other_field == 'pk' or other_field == 'PK':
|
||||
# skip checking the primary key field, as it may be validly fetched by providers to use as feature id
|
||||
continue
|
||||
self.assertEqual(f[other_field], NULL, 'Value for field "{}" was present when it should not have been fetched by request'.format(other_field))
|
||||
|
||||
def testGetFeaturesNoGeometry(self):
|
||||
""" Test that no geometry is present when fetching features without geometry"""
|
||||
|
||||
for f in self.source.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)):
|
||||
self.assertFalse(f.hasGeometry(), 'Expected no geometry, got one')
|
||||
self.assertTrue(f.isValid())
|
||||
|
||||
def testGetFeaturesWithGeometry(self):
|
||||
""" Test that geometry is present when fetching features without setting NoGeometry flag"""
|
||||
for f in self.source.getFeatures(QgsFeatureRequest()):
|
||||
if f['pk'] == 3:
|
||||
# no geometry for this feature
|
||||
continue
|
||||
|
||||
assert f.hasGeometry(), 'Expected geometry, got none'
|
||||
self.assertTrue(f.isValid())
|
@ -29,13 +29,14 @@ from qgis.core import (
|
||||
)
|
||||
|
||||
from utilities import compareWkt
|
||||
from featuresourcetestbase import FeatureSourceTestCase
|
||||
|
||||
|
||||
class ProviderTestCase(object):
|
||||
class ProviderTestCase(FeatureSourceTestCase):
|
||||
|
||||
'''
|
||||
This is a collection of tests for vector data providers and kept generic.
|
||||
To make use of it, subclass it and set self.provider to a provider you want to test.
|
||||
To make use of it, subclass it and set self.source to a provider you want to test.
|
||||
Make sure that your provider uses the default dataset by converting one of the provided datasets from the folder
|
||||
tests/testdata/provider to a dataset your provider is able to handle.
|
||||
|
||||
@ -44,73 +45,6 @@ class ProviderTestCase(object):
|
||||
evaluation are equal.
|
||||
'''
|
||||
|
||||
def testGetFeatures(self, provider=None, extra_features=[], skip_features=[], changed_attributes={}, changed_geometries={}):
|
||||
""" Test that expected results are returned when fetching all features """
|
||||
|
||||
# IMPORTANT - we do not use `for f in provider.getFeatures()` as we are also
|
||||
# testing that existing attributes & geometry in f are overwritten correctly
|
||||
# (for f in ... uses a new QgsFeature for every iteration)
|
||||
|
||||
if not provider:
|
||||
provider = self.provider
|
||||
|
||||
it = provider.getFeatures()
|
||||
f = QgsFeature()
|
||||
attributes = {}
|
||||
geometries = {}
|
||||
while it.nextFeature(f):
|
||||
# expect feature to be valid
|
||||
self.assertTrue(f.isValid())
|
||||
# split off the first 5 attributes only - some provider test datasets will include
|
||||
# additional attributes which we ignore
|
||||
attrs = f.attributes()[0:5]
|
||||
# force the num_char attribute to be text - some providers (e.g., delimited text) will
|
||||
# automatically detect that this attribute contains numbers and set it as a numeric
|
||||
# field
|
||||
attrs[4] = str(attrs[4])
|
||||
attributes[f['pk']] = attrs
|
||||
geometries[f['pk']] = f.hasGeometry() and f.geometry().exportToWkt()
|
||||
|
||||
expected_attributes = {5: [5, -200, NULL, 'NuLl', '5'],
|
||||
3: [3, 300, 'Pear', 'PEaR', '3'],
|
||||
1: [1, 100, 'Orange', 'oranGe', '1'],
|
||||
2: [2, 200, 'Apple', 'Apple', '2'],
|
||||
4: [4, 400, 'Honey', 'Honey', '4']}
|
||||
|
||||
expected_geometries = {1: 'Point (-70.332 66.33)',
|
||||
2: 'Point (-68.2 70.8)',
|
||||
3: None,
|
||||
4: 'Point(-65.32 78.3)',
|
||||
5: 'Point(-71.123 78.23)'}
|
||||
for f in extra_features:
|
||||
expected_attributes[f[0]] = f.attributes()
|
||||
if f.hasGeometry():
|
||||
expected_geometries[f[0]] = f.geometry().exportToWkt()
|
||||
else:
|
||||
expected_geometries[f[0]] = None
|
||||
|
||||
for i in skip_features:
|
||||
del expected_attributes[i]
|
||||
del expected_geometries[i]
|
||||
for i, a in changed_attributes.items():
|
||||
for attr_idx, v in a.items():
|
||||
expected_attributes[i][attr_idx] = v
|
||||
for i, g, in changed_geometries.items():
|
||||
if g:
|
||||
expected_geometries[i] = g.exportToWkt()
|
||||
else:
|
||||
expected_geometries[i] = None
|
||||
|
||||
self.assertEqual(attributes, expected_attributes, 'Expected {}, got {}'.format(expected_attributes, attributes))
|
||||
|
||||
self.assertEqual(len(expected_geometries), len(geometries))
|
||||
|
||||
for pk, geom in list(expected_geometries.items()):
|
||||
if geom:
|
||||
assert compareWkt(geom, geometries[pk]), "Geometry {} mismatch Expected:\n{}\nGot:\n{}\n".format(pk, geom, geometries[pk])
|
||||
else:
|
||||
self.assertFalse(geometries[pk], 'Expected null geometry for {}'.format(pk))
|
||||
|
||||
def uncompiledFilters(self):
|
||||
""" Individual derived provider tests should override this to return a list of expressions which
|
||||
cannot be compiled """
|
||||
@ -121,15 +55,12 @@ class ProviderTestCase(object):
|
||||
should be partially compiled """
|
||||
return set()
|
||||
|
||||
def assert_query(self, provider, expression, expected):
|
||||
request = QgsFeatureRequest().setFilterExpression(expression).setFlags(QgsFeatureRequest.NoGeometry)
|
||||
result = set([f['pk'] for f in provider.getFeatures(request)])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}"'.format(set(expected), result, expression)
|
||||
self.assertTrue(all(f.isValid() for f in provider.getFeatures(request)))
|
||||
def assert_query(self, source, expression, expected):
|
||||
FeatureSourceTestCase.assert_query(self, source, expression, expected)
|
||||
|
||||
if self.compiled:
|
||||
# Check compilation status
|
||||
it = provider.getFeatures(QgsFeatureRequest().setFilterExpression(expression))
|
||||
it = source.getFeatures(QgsFeatureRequest().setFilterExpression(expression))
|
||||
|
||||
if expression in self.uncompiledFilters():
|
||||
self.assertEqual(it.compileStatus(), QgsAbstractFeatureIterator.NoCompilation)
|
||||
@ -138,145 +69,8 @@ class ProviderTestCase(object):
|
||||
else:
|
||||
self.assertEqual(it.compileStatus(), QgsAbstractFeatureIterator.Compiled)
|
||||
|
||||
# Also check that filter works when referenced fields are not being retrieved by request
|
||||
result = set([f['pk'] for f in provider.getFeatures(QgsFeatureRequest().setFilterExpression(expression).setSubsetOfAttributes([0]))])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing expression "{}" using empty attribute subset'.format(set(expected), result, expression)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
request = QgsFeatureRequest().setFilterExpression(expression)
|
||||
for f in provider.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)
|
||||
|
||||
def runGetFeatureTests(self, provider):
|
||||
assert len([f for f in provider.getFeatures()]) == 5
|
||||
self.assert_query(provider, 'name ILIKE \'QGIS\'', [])
|
||||
self.assert_query(provider, '"name" IS NULL', [5])
|
||||
self.assert_query(provider, '"name" IS NOT NULL', [1, 2, 3, 4])
|
||||
self.assert_query(provider, '"name" NOT LIKE \'Ap%\'', [1, 3, 4])
|
||||
self.assert_query(provider, '"name" NOT ILIKE \'QGIS\'', [1, 2, 3, 4])
|
||||
self.assert_query(provider, '"name" NOT ILIKE \'pEAR\'', [1, 2, 4])
|
||||
self.assert_query(provider, 'name = \'Apple\'', [2])
|
||||
self.assert_query(provider, 'name <> \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(provider, 'name = \'apple\'', [])
|
||||
self.assert_query(provider, '"name" <> \'apple\'', [1, 2, 3, 4])
|
||||
self.assert_query(provider, '(name = \'Apple\') is not null', [1, 2, 3, 4])
|
||||
self.assert_query(provider, 'name LIKE \'Apple\'', [2])
|
||||
self.assert_query(provider, 'name LIKE \'aPple\'', [])
|
||||
self.assert_query(provider, 'name ILIKE \'aPple\'', [2])
|
||||
self.assert_query(provider, 'name ILIKE \'%pp%\'', [2])
|
||||
self.assert_query(provider, 'cnt > 0', [1, 2, 3, 4])
|
||||
self.assert_query(provider, '-cnt > 0', [5])
|
||||
self.assert_query(provider, 'cnt < 0', [5])
|
||||
self.assert_query(provider, '-cnt < 0', [1, 2, 3, 4])
|
||||
self.assert_query(provider, 'cnt >= 100', [1, 2, 3, 4])
|
||||
self.assert_query(provider, 'cnt <= 100', [1, 5])
|
||||
self.assert_query(provider, 'pk IN (1, 2, 4, 8)', [1, 2, 4])
|
||||
self.assert_query(provider, 'cnt = 50 * 2', [1])
|
||||
self.assert_query(provider, 'cnt = 150 / 1.5', [1])
|
||||
self.assert_query(provider, 'cnt = 1000 / 10', [1])
|
||||
self.assert_query(provider, 'cnt = 1000/11+10', []) # checks that provider isn't rounding int/int
|
||||
self.assert_query(provider, 'pk = 9 // 4', [2]) # int division
|
||||
self.assert_query(provider, 'cnt = 99 + 1', [1])
|
||||
self.assert_query(provider, 'cnt = 101 - 1', [1])
|
||||
self.assert_query(provider, 'cnt - 1 = 99', [1])
|
||||
self.assert_query(provider, '-cnt - 1 = -101', [1])
|
||||
self.assert_query(provider, '-(-cnt) = 100', [1])
|
||||
self.assert_query(provider, '-(cnt) = -(100)', [1])
|
||||
self.assert_query(provider, 'cnt + 1 = 101', [1])
|
||||
self.assert_query(provider, 'cnt = 1100 % 1000', [1])
|
||||
self.assert_query(provider, '"name" || \' \' || "name" = \'Orange Orange\'', [1])
|
||||
self.assert_query(provider, '"name" || \' \' || "cnt" = \'Orange 100\'', [1])
|
||||
self.assert_query(provider, '\'x\' || "name" IS NOT NULL', [1, 2, 3, 4])
|
||||
self.assert_query(provider, '\'x\' || "name" IS NULL', [5])
|
||||
self.assert_query(provider, 'cnt = 10 ^ 2', [1])
|
||||
self.assert_query(provider, '"name" ~ \'[OP]ra[gne]+\'', [1])
|
||||
self.assert_query(provider, '"name"="name2"', [2, 4]) # mix of matched and non-matched case sensitive names
|
||||
self.assert_query(provider, 'true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'false', [])
|
||||
|
||||
# Three value logic
|
||||
self.assert_query(provider, 'false and false', [])
|
||||
self.assert_query(provider, 'false and true', [])
|
||||
self.assert_query(provider, 'false and NULL', [])
|
||||
self.assert_query(provider, 'true and false', [])
|
||||
self.assert_query(provider, 'true and true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'true and NULL', [])
|
||||
self.assert_query(provider, 'NULL and false', [])
|
||||
self.assert_query(provider, 'NULL and true', [])
|
||||
self.assert_query(provider, 'NULL and NULL', [])
|
||||
self.assert_query(provider, 'false or false', [])
|
||||
self.assert_query(provider, 'false or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'false or NULL', [])
|
||||
self.assert_query(provider, 'true or false', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'true or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'true or NULL', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'NULL or false', [])
|
||||
self.assert_query(provider, 'NULL or true', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'NULL or NULL', [])
|
||||
self.assert_query(provider, 'not true', [])
|
||||
self.assert_query(provider, 'not false', [1, 2, 3, 4, 5])
|
||||
self.assert_query(provider, 'not null', [])
|
||||
|
||||
# not
|
||||
self.assert_query(provider, 'not name = \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(provider, 'not name IS NULL', [1, 2, 3, 4])
|
||||
self.assert_query(provider, 'not name = \'Apple\' or name = \'Apple\'', [1, 2, 3, 4])
|
||||
self.assert_query(provider, 'not name = \'Apple\' or not name = \'Apple\'', [1, 3, 4])
|
||||
self.assert_query(provider, 'not name = \'Apple\' and pk = 4', [4])
|
||||
self.assert_query(provider, 'not name = \'Apple\' and not pk = 4', [1, 3])
|
||||
self.assert_query(provider, 'not pk IN (1, 2, 4, 8)', [3, 5])
|
||||
|
||||
# type conversion - QGIS expressions do not mind that we are comparing a string
|
||||
# against numeric literals
|
||||
self.assert_query(provider, 'num_char IN (2, 4, 5)', [2, 4, 5])
|
||||
|
||||
#function
|
||||
self.assert_query(provider, 'sqrt(pk) >= 2', [4, 5])
|
||||
self.assert_query(provider, 'radians(cnt) < 2', [1, 5])
|
||||
self.assert_query(provider, 'degrees(pk) <= 200', [1, 2, 3])
|
||||
self.assert_query(provider, 'abs(cnt) <= 200', [1, 2, 5])
|
||||
self.assert_query(provider, 'cos(pk) < 0', [2, 3, 4])
|
||||
self.assert_query(provider, 'sin(pk) < 0', [4, 5])
|
||||
self.assert_query(provider, 'tan(pk) < 0', [2, 3, 5])
|
||||
self.assert_query(provider, 'acos(-1) < pk', [4, 5])
|
||||
self.assert_query(provider, 'asin(1) < pk', [2, 3, 4, 5])
|
||||
self.assert_query(provider, 'atan(3.14) < pk', [2, 3, 4, 5])
|
||||
self.assert_query(provider, 'atan2(3.14, pk) < 1', [3, 4, 5])
|
||||
self.assert_query(provider, 'exp(pk) < 10', [1, 2])
|
||||
self.assert_query(provider, 'ln(pk) <= 1', [1, 2])
|
||||
self.assert_query(provider, 'log(3, pk) <= 1', [1, 2, 3])
|
||||
self.assert_query(provider, 'log10(pk) < 0.5', [1, 2, 3])
|
||||
self.assert_query(provider, 'round(3.14) <= pk', [3, 4, 5])
|
||||
self.assert_query(provider, 'round(0.314,1) * 10 = pk', [3])
|
||||
self.assert_query(provider, 'floor(3.14) <= pk', [3, 4, 5])
|
||||
self.assert_query(provider, 'ceil(3.14) <= pk', [4, 5])
|
||||
self.assert_query(provider, 'pk < pi()', [1, 2, 3])
|
||||
|
||||
self.assert_query(provider, 'round(cnt / 66.67) <= 2', [1, 5])
|
||||
self.assert_query(provider, 'floor(cnt / 66.67) <= 2', [1, 2, 5])
|
||||
self.assert_query(provider, 'ceil(cnt / 66.67) <= 2', [1, 5])
|
||||
self.assert_query(provider, 'pk < pi() / 2', [1])
|
||||
self.assert_query(provider, 'pk = char(51)', [3])
|
||||
self.assert_query(provider, 'pk = coalesce(NULL,3,4)', [3])
|
||||
self.assert_query(provider, 'lower(name) = \'apple\'', [2])
|
||||
self.assert_query(provider, 'upper(name) = \'APPLE\'', [2])
|
||||
self.assert_query(provider, 'name = trim(\' Apple \')', [2])
|
||||
|
||||
# geometry
|
||||
# azimuth and touches tests are deactivated because they do not pass for WFS provider
|
||||
#self.assert_query(provider, 'azimuth($geometry,geom_from_wkt( \'Point (-70 70)\')) < pi()', [1, 5])
|
||||
self.assert_query(provider, 'x($geometry) < -70', [1, 5])
|
||||
self.assert_query(provider, 'y($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(provider, 'xmin($geometry) < -70', [1, 5])
|
||||
self.assert_query(provider, 'ymin($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(provider, 'xmax($geometry) < -70', [1, 5])
|
||||
self.assert_query(provider, 'ymax($geometry) > 70', [2, 4, 5])
|
||||
self.assert_query(provider, 'disjoint($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [4, 5])
|
||||
self.assert_query(provider, 'intersects($geometry,geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'))', [1, 2])
|
||||
#self.assert_query(provider, 'touches($geometry,geom_from_wkt( \'Polygon ((-70.332 66.33, -65.32 66.33, -65.32 78.3, -70.332 78.3, -70.332 66.33))\'))', [1, 4])
|
||||
self.assert_query(provider, 'contains(geom_from_wkt( \'Polygon ((-72.2 66.1, -65.2 66.1, -65.2 72.0, -72.2 72.0, -72.2 66.1))\'),$geometry)', [1, 2])
|
||||
self.assert_query(provider, 'distance($geometry,geom_from_wkt( \'Point (-70 70)\')) > 7', [4, 5])
|
||||
self.assert_query(provider, 'intersects($geometry,geom_from_gml( \'<gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>-72.2,66.1 -65.2,66.1 -65.2,72.0 -72.2,72.0 -72.2,66.1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon>\'))', [1, 2])
|
||||
def runGetFeatureTests(self, source):
|
||||
FeatureSourceTestCase.runGetFeatureTests(self, source)
|
||||
|
||||
# combination of an uncompilable expression and limit
|
||||
feature = next(self.vl.getFeatures('pk=4'))
|
||||
@ -322,61 +116,61 @@ class ProviderTestCase(object):
|
||||
self.disableCompiler()
|
||||
except AttributeError:
|
||||
pass
|
||||
self.runGetFeatureTests(self.provider)
|
||||
self.runGetFeatureTests(self.source)
|
||||
if hasattr(self, 'poly_provider'):
|
||||
self.runPolyGetFeatureTests(self.poly_provider)
|
||||
|
||||
def testPolyGetFeaturesCompiled(self):
|
||||
def testGetFeaturesExp(self):
|
||||
try:
|
||||
self.enableCompiler()
|
||||
self.compiled = True
|
||||
self.runGetFeatureTests(self.provider)
|
||||
self.runGetFeatureTests(self.source)
|
||||
if hasattr(self, 'poly_provider'):
|
||||
self.runPolyGetFeatureTests(self.poly_provider)
|
||||
except AttributeError:
|
||||
print('Provider does not support compiling')
|
||||
|
||||
def testSubsetString(self):
|
||||
if not self.provider.supportsSubsetString():
|
||||
if not self.source.supportsSubsetString():
|
||||
print('Provider does not support subset strings')
|
||||
return
|
||||
|
||||
subset = self.getSubsetString()
|
||||
self.provider.setSubsetString(subset)
|
||||
self.assertEqual(self.provider.subsetString(), subset)
|
||||
result = set([f['pk'] for f in self.provider.getFeatures()])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures()))
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
self.assertEqual(self.source.subsetString(), subset)
|
||||
result = set([f['pk'] for f in self.source.getFeatures()])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures()))
|
||||
self.source.setSubsetString(None)
|
||||
|
||||
expected = set([2, 3, 4])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# Subset string AND filter rect
|
||||
self.provider.setSubsetString(subset)
|
||||
self.source.setSubsetString(subset)
|
||||
extent = QgsRectangle(-70, 70, -60, 75)
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
result = set([f['pk'] for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
self.provider.setSubsetString(None)
|
||||
result = set([f['pk'] for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
self.source.setSubsetString(None)
|
||||
expected = set([2])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# Subset string AND filter rect, version 2
|
||||
self.provider.setSubsetString(subset)
|
||||
self.source.setSubsetString(subset)
|
||||
extent = QgsRectangle(-71, 65, -60, 80)
|
||||
result = set([f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))])
|
||||
self.provider.setSubsetString(None)
|
||||
result = set([f['pk'] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))])
|
||||
self.source.setSubsetString(None)
|
||||
expected = set([2, 4])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
|
||||
|
||||
# Subset string AND expression
|
||||
self.provider.setSubsetString(subset)
|
||||
self.source.setSubsetString(subset)
|
||||
request = QgsFeatureRequest().setFilterExpression('length("name")=5')
|
||||
result = set([f['pk'] for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
self.provider.setSubsetString(None)
|
||||
result = set([f['pk'] for f in self.source.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
self.source.setSubsetString(None)
|
||||
expected = set([2, 4])
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing subset string {}'.format(set(expected), result, subset)
|
||||
self.assertTrue(all_valid)
|
||||
@ -389,7 +183,7 @@ class ProviderTestCase(object):
|
||||
"""Individual providers may need to override this depending on their subset string formats"""
|
||||
return '"cnt" > 100 and "cnt" < 400'
|
||||
|
||||
def testOrderByUncompiled(self):
|
||||
def testOrderBy(self):
|
||||
try:
|
||||
self.disableCompiler()
|
||||
except AttributeError:
|
||||
@ -404,74 +198,7 @@ class ProviderTestCase(object):
|
||||
print('Provider does not support compiling')
|
||||
|
||||
def runOrderByTests(self):
|
||||
request = QgsFeatureRequest().addOrderBy('cnt')
|
||||
values = [f['cnt'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [-200, 100, 200, 300, 400])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('cnt', False)
|
||||
values = [f['cnt'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [400, 300, 200, 100, -200])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name')
|
||||
values = [f['name'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, ['Apple', 'Honey', 'Orange', 'Pear', NULL])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', True, True)
|
||||
values = [f['name'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [NULL, 'Apple', 'Honey', 'Orange', 'Pear'])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', False)
|
||||
values = [f['name'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [NULL, 'Pear', 'Orange', 'Honey', 'Apple'])
|
||||
|
||||
request = QgsFeatureRequest().addOrderBy('name', False, False)
|
||||
values = [f['name'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, ['Pear', 'Orange', 'Honey', 'Apple', NULL])
|
||||
|
||||
# Case sensitivity
|
||||
request = QgsFeatureRequest().addOrderBy('name2')
|
||||
values = [f['name2'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, ['Apple', 'Honey', 'NuLl', 'oranGe', 'PEaR'])
|
||||
|
||||
# Combination with LIMIT
|
||||
request = QgsFeatureRequest().addOrderBy('pk', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
|
||||
# A slightly more complex expression
|
||||
request = QgsFeatureRequest().addOrderBy('pk*2', False)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4, 3, 2, 1])
|
||||
|
||||
# Order reversing expression
|
||||
request = QgsFeatureRequest().addOrderBy('pk*-1', False)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [1, 2, 3, 4, 5])
|
||||
|
||||
# Type dependent expression
|
||||
request = QgsFeatureRequest().addOrderBy('num_char*2', False)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4, 3, 2, 1])
|
||||
|
||||
# Order by guaranteed to fail
|
||||
request = QgsFeatureRequest().addOrderBy('not a valid expression*', False)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(set(values), set([5, 4, 3, 2, 1]))
|
||||
|
||||
# Multiple order bys and boolean
|
||||
request = QgsFeatureRequest().addOrderBy('pk > 2').addOrderBy('pk', False)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [2, 1, 5, 4, 3])
|
||||
|
||||
# Multiple order bys, one bad, and a limit
|
||||
request = QgsFeatureRequest().addOrderBy('pk', False).addOrderBy('not a valid expression*', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
|
||||
# Bad expression first
|
||||
request = QgsFeatureRequest().addOrderBy('not a valid expression*', False).addOrderBy('pk', False).setLimit(2)
|
||||
values = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
self.assertEqual(values, [5, 4])
|
||||
FeatureSourceTestCase.runOrderByTests(self)
|
||||
|
||||
# Combination with subset of attributes
|
||||
request = QgsFeatureRequest().addOrderBy('num_char', False).setSubsetOfAttributes(['pk'], self.vl.fields())
|
||||
@ -502,100 +229,6 @@ class ProviderTestCase(object):
|
||||
pks.append(f['pk'])
|
||||
self.assertEqual(set(pks), {1, 2, 3, 4, 5})
|
||||
|
||||
def testGetFeaturesFidTests(self):
|
||||
fids = [f.id() for f in self.provider.getFeatures()]
|
||||
assert len(fids) == 5, 'Expected 5 features, got {} instead'.format(len(fids))
|
||||
for id in fids:
|
||||
features = [f for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFid(id))]
|
||||
self.assertEqual(len(features), 1)
|
||||
feature = features[0]
|
||||
self.assertTrue(feature.isValid())
|
||||
|
||||
result = [feature.id()]
|
||||
expected = [id]
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature ID filter'.format(expected, result)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
request = QgsFeatureRequest().setFilterFid(id)
|
||||
for f in self.provider.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f.id() == id)
|
||||
|
||||
# bad features
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setFilterFid(-99999999))
|
||||
feature = QgsFeature(5)
|
||||
feature.setValid(False)
|
||||
self.assertFalse(it.nextFeature(feature))
|
||||
self.assertFalse(feature.isValid())
|
||||
|
||||
def testGetFeaturesFidsTests(self):
|
||||
fids = [f.id() for f in self.provider.getFeatures()]
|
||||
self.assertEqual(len(fids), 5)
|
||||
|
||||
request = QgsFeatureRequest().setFilterFids([fids[0], fids[2]])
|
||||
result = set([f.id() for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
expected = set([fids[0], fids[2]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.provider.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f.id() in expected)
|
||||
|
||||
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]]))])
|
||||
expected = set([fids[1], fids[3], fids[4]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
#providers should ignore non-existent fids
|
||||
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([-101, fids[1], -102, fids[3], -103, fids[4], -104]))])
|
||||
expected = set([fids[1], fids[3], fids[4]])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
result = set([f.id() for f in self.provider.getFeatures(QgsFeatureRequest().setFilterFids([]))])
|
||||
expected = set([])
|
||||
assert result == expected, 'Expected {} and got {} when testing for feature IDs filter'.format(expected, result)
|
||||
|
||||
# Rewind mid-way
|
||||
request = QgsFeatureRequest().setFilterFids([fids[1], fids[3], fids[4]])
|
||||
feature_it = self.provider.getFeatures(request)
|
||||
feature = QgsFeature()
|
||||
feature.setValid(True)
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertIn(feature.id(), [fids[1], fids[3], fids[4]])
|
||||
first_feature = feature
|
||||
self.assertTrue(feature.isValid())
|
||||
# rewind
|
||||
self.assertTrue(feature_it.rewind())
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertEqual(feature.id(), first_feature.id())
|
||||
self.assertTrue(feature.isValid())
|
||||
# grab all features
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
self.assertTrue(feature_it.nextFeature(feature))
|
||||
# none left
|
||||
self.assertFalse(feature_it.nextFeature(feature))
|
||||
self.assertFalse(feature.isValid())
|
||||
|
||||
def testGetFeaturesFilterRectTests(self):
|
||||
extent = QgsRectangle(-70, 67, -60, 80)
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
features = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
assert set(features) == set([2, 4]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.provider.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in set([2, 4]))
|
||||
|
||||
# test with an empty rectangle
|
||||
extent = QgsRectangle()
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
features = [f['pk'] for f in self.provider.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
def testGetFeaturesPolyFilterRectTests(self):
|
||||
""" Test fetching features from a polygon layer with filter rect"""
|
||||
try:
|
||||
@ -607,7 +240,7 @@ class ProviderTestCase(object):
|
||||
extent = QgsRectangle(-73, 70, -63, 80)
|
||||
request = QgsFeatureRequest().setFilterRect(extent)
|
||||
features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
# Some providers may return the exact intersection matches (2, 3) even without the ExactIntersect flag, so we accept that too
|
||||
assert set(features) == set([2, 3]) or set(features) == set([1, 2, 3]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
@ -615,201 +248,88 @@ class ProviderTestCase(object):
|
||||
# Test with exact intersection
|
||||
request = QgsFeatureRequest().setFilterRect(extent).setFlags(QgsFeatureRequest.ExactIntersect)
|
||||
features = [f['pk'] for f in self.poly_provider.getFeatures(request)]
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
all_valid = (all(f.isValid() for f in self.source.getFeatures(request)))
|
||||
assert set(features) == set([2, 3]), 'Got {} instead'.format(features)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test with an empty rectangle
|
||||
extent = QgsRectangle()
|
||||
features = [f['pk'] for f in self.provider.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
|
||||
features = [f['pk'] for f in self.source.getFeatures(QgsFeatureRequest().setFilterRect(extent))]
|
||||
assert set(features) == set([1, 2, 3, 4, 5]), 'Got {} instead'.format(features)
|
||||
|
||||
def testRectAndExpression(self):
|
||||
extent = QgsRectangle(-70, 67, -60, 80)
|
||||
request = QgsFeatureRequest().setFilterExpression('"cnt">200').setFilterRect(extent)
|
||||
result = set([f['pk'] for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
expected = [4]
|
||||
assert set(expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(set(expected), result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# shouldn't matter what order this is done in
|
||||
request = QgsFeatureRequest().setFilterRect(extent).setFilterExpression('"cnt">200')
|
||||
result = set([f['pk'] for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
expected = [4]
|
||||
assert set(
|
||||
expected) == result, 'Expected {} and got {} when testing for combination of filterRect and expression'.format(
|
||||
set(expected), result)
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
# test that results match QgsFeatureRequest.acceptFeature
|
||||
for f in self.provider.getFeatures():
|
||||
self.assertEqual(request.acceptFeature(f), f['pk'] in expected)
|
||||
|
||||
def testGetFeaturesLimit(self):
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(2))
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features, got {} instead'.format(len(features))
|
||||
# fetch one feature
|
||||
feature = QgsFeature()
|
||||
assert not it.nextFeature(feature), 'Expected no feature after limit, got one'
|
||||
it.rewind()
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features after rewind, got {} instead'.format(len(features))
|
||||
it.rewind()
|
||||
assert it.nextFeature(feature), 'Expected feature after rewind, got none'
|
||||
it.rewind()
|
||||
features = [f['pk'] for f in it]
|
||||
assert len(features) == 2, 'Expected two features after rewind, got {} instead'.format(len(features))
|
||||
# test with expression, both with and without compilation
|
||||
try:
|
||||
self.disableCompiler()
|
||||
except AttributeError:
|
||||
pass
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(2).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
try:
|
||||
self.enableCompiler()
|
||||
except AttributeError:
|
||||
pass
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(2).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
# limit to more features than exist
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(3).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert set(features) == set([1, 5]), 'Expected [1,5] for expression and feature limit, Got {} instead'.format(features)
|
||||
# limit to less features than possible
|
||||
it = self.provider.getFeatures(QgsFeatureRequest().setLimit(1).setFilterExpression('cnt <= 100'))
|
||||
features = [f['pk'] for f in it]
|
||||
assert 1 in features or 5 in features, 'Expected either 1 or 5 for expression and feature limit, Got {} instead'.format(features)
|
||||
|
||||
def testMinValue(self):
|
||||
self.assertEqual(self.provider.minimumValue(1), -200)
|
||||
self.assertEqual(self.provider.minimumValue(2), 'Apple')
|
||||
self.assertEqual(self.source.minimumValue(1), -200)
|
||||
self.assertEqual(self.source.minimumValue(2), 'Apple')
|
||||
|
||||
subset = self.getSubsetString()
|
||||
self.provider.setSubsetString(subset)
|
||||
min_value = self.provider.minimumValue(1)
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
min_value = self.source.minimumValue(1)
|
||||
self.source.setSubsetString(None)
|
||||
self.assertEqual(min_value, 200)
|
||||
|
||||
def testMaxValue(self):
|
||||
self.assertEqual(self.provider.maximumValue(1), 400)
|
||||
self.assertEqual(self.provider.maximumValue(2), 'Pear')
|
||||
self.assertEqual(self.source.maximumValue(1), 400)
|
||||
self.assertEqual(self.source.maximumValue(2), 'Pear')
|
||||
|
||||
subset = self.getSubsetString2()
|
||||
self.provider.setSubsetString(subset)
|
||||
max_value = self.provider.maximumValue(1)
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
max_value = self.source.maximumValue(1)
|
||||
self.source.setSubsetString(None)
|
||||
self.assertEqual(max_value, 300)
|
||||
|
||||
def testExtent(self):
|
||||
reference = QgsGeometry.fromRect(
|
||||
QgsRectangle(-71.123, 66.33, -65.32, 78.3))
|
||||
provider_extent = QgsGeometry.fromRect(self.provider.extent())
|
||||
provider_extent = QgsGeometry.fromRect(self.source.extent())
|
||||
|
||||
self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001))
|
||||
|
||||
def testUnique(self):
|
||||
self.assertEqual(set(self.provider.uniqueValues(1)), set([-200, 100, 200, 300, 400]))
|
||||
assert set(['Apple', 'Honey', 'Orange', 'Pear', NULL]) == set(self.provider.uniqueValues(2)), 'Got {}'.format(set(self.provider.uniqueValues(2)))
|
||||
self.assertEqual(set(self.source.uniqueValues(1)), set([-200, 100, 200, 300, 400]))
|
||||
assert set(['Apple', 'Honey', 'Orange', 'Pear', NULL]) == set(self.source.uniqueValues(2)), 'Got {}'.format(set(self.source.uniqueValues(2)))
|
||||
|
||||
subset = self.getSubsetString2()
|
||||
self.provider.setSubsetString(subset)
|
||||
values = self.provider.uniqueValues(1)
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
values = self.source.uniqueValues(1)
|
||||
self.source.setSubsetString(None)
|
||||
self.assertEqual(set(values), set([200, 300]))
|
||||
|
||||
def testUniqueStringsMatching(self):
|
||||
self.assertEqual(set(self.provider.uniqueStringsMatching(2, 'a')), set(['Pear', 'Orange', 'Apple']))
|
||||
self.assertEqual(set(self.source.uniqueStringsMatching(2, 'a')), set(['Pear', 'Orange', 'Apple']))
|
||||
# test case insensitive
|
||||
self.assertEqual(set(self.provider.uniqueStringsMatching(2, 'A')), set(['Pear', 'Orange', 'Apple']))
|
||||
self.assertEqual(set(self.source.uniqueStringsMatching(2, 'A')), set(['Pear', 'Orange', 'Apple']))
|
||||
# test string ending in substring
|
||||
self.assertEqual(set(self.provider.uniqueStringsMatching(2, 'ney')), set(['Honey']))
|
||||
self.assertEqual(set(self.source.uniqueStringsMatching(2, 'ney')), set(['Honey']))
|
||||
# test limit
|
||||
result = set(self.provider.uniqueStringsMatching(2, 'a', 2))
|
||||
result = set(self.source.uniqueStringsMatching(2, 'a', 2))
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertTrue(result.issubset(set(['Pear', 'Orange', 'Apple'])))
|
||||
|
||||
assert set([u'Apple', u'Honey', u'Orange', u'Pear', NULL]) == set(self.provider.uniqueValues(2)), 'Got {}'.format(set(self.provider.uniqueValues(2)))
|
||||
assert set([u'Apple', u'Honey', u'Orange', u'Pear', NULL]) == set(self.source.uniqueValues(2)), 'Got {}'.format(set(self.source.uniqueValues(2)))
|
||||
|
||||
subset = self.getSubsetString2()
|
||||
self.provider.setSubsetString(subset)
|
||||
values = self.provider.uniqueStringsMatching(2, 'a')
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
values = self.source.uniqueStringsMatching(2, 'a')
|
||||
self.source.setSubsetString(None)
|
||||
self.assertEqual(set(values), set(['Pear', 'Apple']))
|
||||
|
||||
def testFeatureCount(self):
|
||||
assert self.provider.featureCount() == 5, 'Got {}'.format(self.provider.featureCount())
|
||||
assert self.source.featureCount() == 5, 'Got {}'.format(self.source.featureCount())
|
||||
|
||||
#Add a subset string and test feature count
|
||||
subset = self.getSubsetString()
|
||||
self.provider.setSubsetString(subset)
|
||||
count = self.provider.featureCount()
|
||||
self.provider.setSubsetString(None)
|
||||
self.source.setSubsetString(subset)
|
||||
count = self.source.featureCount()
|
||||
self.source.setSubsetString(None)
|
||||
assert count == 3, 'Got {}'.format(count)
|
||||
|
||||
def testClosedIterators(self):
|
||||
""" Test behavior of closed iterators """
|
||||
|
||||
# Test retrieving feature after closing iterator
|
||||
f_it = self.provider.getFeatures(QgsFeatureRequest())
|
||||
fet = QgsFeature()
|
||||
assert f_it.nextFeature(fet), 'Could not fetch feature'
|
||||
assert fet.isValid(), 'Feature is not valid'
|
||||
assert f_it.close(), 'Could not close iterator'
|
||||
self.assertFalse(f_it.nextFeature(fet), 'Fetched feature after iterator closed, expected nextFeature() to return False')
|
||||
self.assertFalse(fet.isValid(), 'Valid feature fetched from closed iterator, should be invalid')
|
||||
|
||||
# Test rewinding closed iterator
|
||||
self.assertFalse(f_it.rewind(), 'Rewinding closed iterator successful, should not be allowed')
|
||||
|
||||
def testGetFeaturesSubsetAttributes(self):
|
||||
""" Test that expected results are returned when using subsets of attributes """
|
||||
|
||||
tests = {'pk': set([1, 2, 3, 4, 5]),
|
||||
'cnt': set([-200, 300, 100, 200, 400]),
|
||||
'name': set(['Pear', 'Orange', 'Apple', 'Honey', NULL]),
|
||||
'name2': set(['NuLl', 'PEaR', 'oranGe', 'Apple', 'Honey'])}
|
||||
for field, expected in list(tests.items()):
|
||||
request = QgsFeatureRequest().setSubsetOfAttributes([field], self.provider.fields())
|
||||
result = set([f[field] for f in self.provider.getFeatures(request)])
|
||||
all_valid = (all(f.isValid() for f in self.provider.getFeatures(request)))
|
||||
self.assertEqual(result, expected, 'Expected {}, got {}'.format(expected, result))
|
||||
self.assertTrue(all_valid)
|
||||
|
||||
def testGetFeaturesSubsetAttributes2(self):
|
||||
""" Test that other fields are NULL when fetching subsets of attributes """
|
||||
|
||||
for field_to_fetch in ['pk', 'cnt', 'name', 'name2']:
|
||||
for f in self.provider.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([field_to_fetch], self.provider.fields())):
|
||||
# Check that all other fields are NULL and force name to lower-case
|
||||
for other_field in [field.name() for field in self.provider.fields() if field.name().lower() != field_to_fetch]:
|
||||
if other_field == 'pk' or other_field == 'PK':
|
||||
# skip checking the primary key field, as it may be validly fetched by providers to use as feature id
|
||||
continue
|
||||
self.assertEqual(f[other_field], NULL, 'Value for field "{}" was present when it should not have been fetched by request'.format(other_field))
|
||||
|
||||
def testGetFeaturesNoGeometry(self):
|
||||
""" Test that no geometry is present when fetching features without geometry"""
|
||||
|
||||
for f in self.provider.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)):
|
||||
for f in self.source.getFeatures(QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry)):
|
||||
self.assertFalse(f.hasGeometry(), 'Expected no geometry, got one')
|
||||
self.assertTrue(f.isValid())
|
||||
|
||||
def testGetFeaturesWithGeometry(self):
|
||||
""" Test that geometry is present when fetching features without setting NoGeometry flag"""
|
||||
for f in self.provider.getFeatures(QgsFeatureRequest()):
|
||||
if f['pk'] == 3:
|
||||
# no geometry for this feature
|
||||
continue
|
||||
|
||||
assert f.hasGeometry(), 'Expected geometry, got none'
|
||||
self.assertTrue(f.isValid())
|
||||
|
||||
def testAddFeature(self):
|
||||
if not getattr(self, 'getEditableLayer', None):
|
||||
return
|
||||
|
@ -39,7 +39,7 @@ class TestPyQgsDb2Provider(unittest.TestCase, ProviderTestCase):
|
||||
# Create test layer
|
||||
cls.vl = QgsVectorLayer(cls.dbconn + ' srid=4326 type=Point table="QGIS_TEST"."SOMEDATA" (GEOM) sql=', 'test', 'DB2')
|
||||
assert(cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
cls.poly_vl = QgsVectorLayer(
|
||||
cls.dbconn + ' srid=4326 type=POLYGON table="QGIS_TEST"."SOME_POLY_DATA" (geom) sql=', 'test', 'DB2')
|
||||
assert(cls.poly_vl.isValid())
|
||||
|
@ -83,7 +83,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
|
||||
# Create test layer
|
||||
cls.vl = cls.createLayer()
|
||||
assert (cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
# poly layer
|
||||
cls.poly_vl = QgsVectorLayer('Polygon?crs=epsg:4326&field=pk:integer&key=pk',
|
||||
@ -415,7 +415,7 @@ class TestPyQgsMemoryProviderIndexed(unittest.TestCase, ProviderTestCase):
|
||||
cls.vl = QgsVectorLayer('Point?crs=epsg:4326&index=yes&field=pk:integer&field=cnt:int8&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
|
||||
'test', 'memory')
|
||||
assert (cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
f1 = QgsFeature()
|
||||
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
|
||||
@ -436,7 +436,7 @@ class TestPyQgsMemoryProviderIndexed(unittest.TestCase, ProviderTestCase):
|
||||
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
|
||||
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
|
||||
|
||||
cls.provider.addFeatures([f1, f2, f3, f4, f5])
|
||||
cls.source.addFeatures([f1, f2, f3, f4, f5])
|
||||
|
||||
# poly layer
|
||||
cls.poly_vl = QgsVectorLayer('Polygon?crs=epsg:4326&index=yes&field=pk:integer&key=pk',
|
||||
|
@ -40,7 +40,7 @@ class TestPyQgsMssqlProvider(unittest.TestCase, ProviderTestCase):
|
||||
cls.vl = QgsVectorLayer(
|
||||
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'mssql')
|
||||
assert(cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
cls.poly_vl = QgsVectorLayer(
|
||||
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=', 'test', 'mssql')
|
||||
assert(cls.poly_vl.isValid())
|
||||
|
@ -40,7 +40,7 @@ class TestPyQgsOracleProvider(unittest.TestCase, ProviderTestCase):
|
||||
cls.vl = QgsVectorLayer(
|
||||
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="QGIS"."SOME_DATA" (GEOM) sql=', 'test', 'oracle')
|
||||
assert(cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
cls.poly_vl = QgsVectorLayer(
|
||||
cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="QGIS"."SOME_POLY_DATA" (GEOM) sql=', 'test', 'oracle')
|
||||
assert(cls.poly_vl.isValid())
|
||||
@ -151,8 +151,8 @@ class TestPyQgsOracleProvider(unittest.TestCase, ProviderTestCase):
|
||||
QDate(2004, 3, 4), QTime(13, 41, 52)))
|
||||
|
||||
def testDefaultValue(self):
|
||||
self.assertEqual(self.provider.defaultValue(1), NULL)
|
||||
self.assertEqual(self.provider.defaultValue(2), "'qgis'")
|
||||
self.assertEqual(self.source.defaultValue(1), NULL)
|
||||
self.assertEqual(self.source.defaultValue(2), "'qgis'")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -51,7 +51,7 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
||||
# Create test layers
|
||||
cls.vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POINT table="qgis_test"."someData" (geom) sql=', 'test', 'postgres')
|
||||
assert cls.vl.isValid()
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
cls.poly_vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'pk\' srid=4326 type=POLYGON table="qgis_test"."some_poly_data" (geom) sql=', 'test', 'postgres')
|
||||
assert cls.poly_vl.isValid()
|
||||
cls.poly_provider = cls.poly_vl.dataProvider()
|
||||
@ -70,7 +70,7 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
||||
cur.close()
|
||||
self.con.commit()
|
||||
|
||||
def getEditableLayer(self):
|
||||
def getSource(self):
|
||||
# create temporary table for edit tests
|
||||
self.execSQLCommand('DROP TABLE IF EXISTS qgis_test."editData" CASCADE')
|
||||
self.execSQLCommand('CREATE TABLE qgis_test."editData" ( pk SERIAL NOT NULL PRIMARY KEY, cnt integer, name text, name2 text, num_char text, geom public.geometry(Point, 4326))')
|
||||
@ -85,6 +85,9 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
||||
'test', 'postgres')
|
||||
return vl
|
||||
|
||||
def getEditableLayer(self):
|
||||
return self.getSource()
|
||||
|
||||
def enableCompiler(self):
|
||||
QgsSettings().setValue('/qgis/compileExpressions', True)
|
||||
|
||||
@ -99,17 +102,17 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
|
||||
|
||||
# HERE GO THE PROVIDER SPECIFIC TESTS
|
||||
def testDefaultValue(self):
|
||||
self.provider.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, True)
|
||||
self.assertIsInstance(self.provider.defaultValue(0), int)
|
||||
self.assertEqual(self.provider.defaultValue(1), NULL)
|
||||
self.assertEqual(self.provider.defaultValue(2), 'qgis')
|
||||
self.provider.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||
self.source.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, True)
|
||||
self.assertIsInstance(self.source.defaultValue(0), int)
|
||||
self.assertEqual(self.source.defaultValue(1), NULL)
|
||||
self.assertEqual(self.source.defaultValue(2), 'qgis')
|
||||
self.source.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||
|
||||
def testDefaultValueClause(self):
|
||||
self.provider.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||
self.assertEqual(self.provider.defaultValueClause(0), 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)')
|
||||
self.assertFalse(self.provider.defaultValueClause(1))
|
||||
self.assertEqual(self.provider.defaultValueClause(2), '\'qgis\'::text')
|
||||
self.source.setProviderProperty(QgsDataProvider.EvaluateDefaultValues, False)
|
||||
self.assertEqual(self.source.defaultValueClause(0), 'nextval(\'qgis_test."someData_pk_seq"\'::regclass)')
|
||||
self.assertFalse(self.source.defaultValueClause(1))
|
||||
self.assertEqual(self.source.defaultValueClause(2), '\'qgis\'::text')
|
||||
|
||||
def testDateTimeTypes(self):
|
||||
vl = QgsVectorLayer('%s table="qgis_test"."date_times" sql=' % (self.dbconn), "testdatetimes", "postgres")
|
||||
@ -727,7 +730,7 @@ class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):
|
||||
# Create test layers
|
||||
cls.vl = QgsVectorLayer(cls.dbconn + ' sslmode=disable key=\'"key1","key2"\' srid=4326 type=POINT table="qgis_test"."someDataCompound" (geom) sql=', 'test', 'postgres')
|
||||
assert cls.vl.isValid()
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
|
@ -63,7 +63,7 @@ class TestPyQgsShapefileProvider(unittest.TestCase, ProviderTestCase):
|
||||
cls.basetestpolyfile = os.path.join(cls.basetestpath, 'shapefile_poly.shp')
|
||||
cls.vl = QgsVectorLayer('{}|layerid=0'.format(cls.basetestfile), 'test', 'ogr')
|
||||
assert(cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
cls.vl_poly = QgsVectorLayer('{}|layerid=0'.format(cls.basetestpolyfile), 'test', 'ogr')
|
||||
assert (cls.vl_poly.isValid())
|
||||
cls.poly_provider = cls.vl_poly.dataProvider()
|
||||
@ -76,7 +76,7 @@ class TestPyQgsShapefileProvider(unittest.TestCase, ProviderTestCase):
|
||||
for dirname in cls.dirs_to_cleanup:
|
||||
shutil.rmtree(dirname, True)
|
||||
|
||||
def getEditableLayer(self):
|
||||
def getSource(self):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
self.dirs_to_cleanup.append(tmpdir)
|
||||
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
|
||||
@ -87,6 +87,9 @@ class TestPyQgsShapefileProvider(unittest.TestCase, ProviderTestCase):
|
||||
vl = QgsVectorLayer('{}|layerid=0'.format(datasource), 'test', 'ogr')
|
||||
return vl
|
||||
|
||||
def getEditableLayer(self):
|
||||
return self.getSource()
|
||||
|
||||
def enableCompiler(self):
|
||||
QgsSettings().setValue('/qgis/compileExpressions', True)
|
||||
|
||||
|
@ -64,7 +64,7 @@ class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
|
||||
# setup provider for base tests
|
||||
cls.vl = QgsVectorLayer('dbname=\'{}/provider/spatialite.db\' table="somedata" (geom) sql='.format(TEST_DATA_DIR), 'test', 'spatialite')
|
||||
assert(cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
cls.vl_poly = QgsVectorLayer('dbname=\'{}/provider/spatialite.db\' table="somepolydata" (geom) sql='.format(TEST_DATA_DIR), 'test', 'spatialite')
|
||||
assert(cls.vl_poly.isValid())
|
||||
@ -165,7 +165,7 @@ class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
|
||||
for dirname in cls.dirs_to_cleanup:
|
||||
shutil.rmtree(dirname, True)
|
||||
|
||||
def getEditableLayer(self):
|
||||
def getSource(self):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
self.dirs_to_cleanup.append(tmpdir)
|
||||
srcpath = os.path.join(TEST_DATA_DIR, 'provider')
|
||||
@ -177,6 +177,9 @@ class TestQgsSpatialiteProvider(unittest.TestCase, ProviderTestCase):
|
||||
'spatialite')
|
||||
return vl
|
||||
|
||||
def getEditableLayer(self):
|
||||
return self.getSource()
|
||||
|
||||
def setUp(self):
|
||||
"""Run before each test."""
|
||||
pass
|
||||
|
@ -58,7 +58,7 @@ class TestQgsVirtualLayerProvider(unittest.TestCase, ProviderTestCase):
|
||||
d.setUid("pk")
|
||||
cls.vl = QgsVectorLayer(d.toString(), 'test', 'virtual')
|
||||
assert (cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
shp_poly = os.path.join(TEST_DATA_DIR, 'provider/shapefile_poly.shp')
|
||||
d = QgsVirtualLayerDefinition()
|
||||
|
@ -130,7 +130,7 @@ class TestPyQgsWFSProvider(unittest.TestCase, ProviderTestCase):
|
||||
# Create test layer
|
||||
cls.vl = QgsVectorLayer("url='http://" + endpoint + "' typename='my:typename'", 'test', 'WFS')
|
||||
assert (cls.vl.isValid())
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
with open(sanitize(endpoint, '?SERVICE=WFS&REQUEST=GetFeature&VERSION=2.0.0&TYPENAMES=my:typename&SRSNAME=urn:ogc:def:crs:EPSG::4326'), 'wb') as f:
|
||||
f.write("""
|
||||
@ -342,6 +342,10 @@ class TestPyQgsWFSProvider(unittest.TestCase, ProviderTestCase):
|
||||
shutil.rmtree(cls.basetestpath, True)
|
||||
cls.vl = None # so as to properly close the provider and remove any temporary file
|
||||
|
||||
def testWkbType(self):
|
||||
"""N/A for WFS provider"""
|
||||
pass
|
||||
|
||||
def testInconsistentUri(self):
|
||||
"""Test a URI with a typename that doesn't match a type of the capabilities"""
|
||||
|
||||
|
@ -149,7 +149,7 @@ class TestQgsDelimitedTextProviderXY(unittest.TestCase, ProviderTestCase):
|
||||
|
||||
cls.vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
|
||||
assert cls.vl.isValid(), "{} is invalid".format(cls.basetestfile)
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
@ -175,7 +175,7 @@ class TestQgsDelimitedTextProviderWKT(unittest.TestCase, ProviderTestCase):
|
||||
|
||||
cls.vl = QgsVectorLayer(url.toString(), 'test', 'delimitedtext')
|
||||
assert cls.vl.isValid(), "{} is invalid".format(cls.basetestfile)
|
||||
cls.provider = cls.vl.dataProvider()
|
||||
cls.source = cls.vl.dataProvider()
|
||||
|
||||
cls.basetestpolyfile = os.path.join(srcpath, 'delimited_wkt_poly.csv')
|
||||
|
||||
|
@ -51,8 +51,10 @@ from qgis.core import (QgsWkbTypes,
|
||||
QgsVectorLayerSimpleLabeling,
|
||||
QgsSingleCategoryDiagramRenderer,
|
||||
QgsDiagramLayerSettings,
|
||||
QgsTextFormat)
|
||||
QgsTextFormat,
|
||||
NULL)
|
||||
from qgis.testing import start_app, unittest
|
||||
from featuresourcetestbase import FeatureSourceTestCase
|
||||
from utilities import unitTestDataPath
|
||||
start_app()
|
||||
|
||||
@ -172,7 +174,54 @@ def dumpEditBuffer(layer):
|
||||
print(("%d | %s" % (f.id(), f.geometry().exportToWkt())))
|
||||
|
||||
|
||||
class TestQgsVectorLayer(unittest.TestCase):
|
||||
class TestQgsVectorLayer(unittest.TestCase, FeatureSourceTestCase):
|
||||
|
||||
@classmethod
|
||||
def getSource(cls):
|
||||
vl = QgsVectorLayer(
|
||||
'Point?crs=epsg:4326&field=pk:integer&field=cnt:integer&field=name:string(0)&field=name2:string(0)&field=num_char:string&key=pk',
|
||||
'test', 'memory')
|
||||
assert (vl.isValid())
|
||||
|
||||
f1 = QgsFeature()
|
||||
f1.setAttributes([5, -200, NULL, 'NuLl', '5'])
|
||||
f1.setGeometry(QgsGeometry.fromWkt('Point (-71.123 78.23)'))
|
||||
|
||||
f2 = QgsFeature()
|
||||
f2.setAttributes([3, 300, 'Pear', 'PEaR', '3'])
|
||||
|
||||
f3 = QgsFeature()
|
||||
f3.setAttributes([1, 100, 'Orange', 'oranGe', '1'])
|
||||
f3.setGeometry(QgsGeometry.fromWkt('Point (-70.332 66.33)'))
|
||||
|
||||
f4 = QgsFeature()
|
||||
f4.setAttributes([2, 200, 'Apple', 'Apple', '2'])
|
||||
f4.setGeometry(QgsGeometry.fromWkt('Point (-68.2 70.8)'))
|
||||
|
||||
f5 = QgsFeature()
|
||||
f5.setAttributes([4, 400, 'Honey', 'Honey', '4'])
|
||||
f5.setGeometry(QgsGeometry.fromWkt('Point (-65.32 78.3)'))
|
||||
|
||||
vl.dataProvider().addFeatures([f1, f2, f3, f4, f5])
|
||||
return vl
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
"""Run before all tests"""
|
||||
# Create test layer for FeatureSourceTestCase
|
||||
cls.source = cls.getSource()
|
||||
|
||||
def testGetFeaturesSubsetAttributes2(self):
|
||||
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
|
||||
its features as direct copies (due to implicit sharing of QgsFeature)
|
||||
"""
|
||||
pass
|
||||
|
||||
def testGetFeaturesNoGeometry(self):
|
||||
""" Override and skip this QgsFeatureSource test. We are using a memory provider, and it's actually more efficient for the memory provider to return
|
||||
its features as direct copies (due to implicit sharing of QgsFeature)
|
||||
"""
|
||||
pass
|
||||
|
||||
def test_FeatureCount(self):
|
||||
myPath = os.path.join(unitTestDataPath(), 'lines.shp')
|
||||
|
Loading…
x
Reference in New Issue
Block a user