mirror of
https://github.com/qgis/QGIS.git
synced 2025-02-25 00:58:06 -05:00
Followup #10912 - detect cycles in joins and reject joins that would create cycle
Cycle would otherwise cause infinite loop when updating fields and it does not make sense
This commit is contained in:
parent
f73b0b61e5
commit
de48dad6e9
@ -279,8 +279,9 @@ class QgsVectorLayer : QgsMapLayer
|
||||
|
||||
/** Joins another vector layer to this layer
|
||||
@param joinInfo join object containing join layer id, target and source field
|
||||
@note added in 1.7 */
|
||||
void addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
@note added in 1.7
|
||||
@note since 2.6 returns bool indicating whether the join can be added */
|
||||
bool addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
|
||||
/** Removes a vector layer join
|
||||
@note added in 1.7 */
|
||||
|
@ -4,12 +4,13 @@ class QgsVectorLayerJoinBuffer : QObject
|
||||
#include <qgsvectorlayerjoinbuffer.h>
|
||||
%End
|
||||
public:
|
||||
QgsVectorLayerJoinBuffer();
|
||||
QgsVectorLayerJoinBuffer( QgsVectorLayer* layer = 0 );
|
||||
~QgsVectorLayerJoinBuffer();
|
||||
|
||||
/**Joins another vector layer to this layer
|
||||
@param joinInfo join object containing join layer id, target and source field */
|
||||
void addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
@param joinInfo join object containing join layer id, target and source field
|
||||
@return (since 2.6) whether the join was successfully added */
|
||||
bool addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
|
||||
/**Removes a vector layer join*/
|
||||
void removeJoin( const QString& joinLayerId );
|
||||
|
@ -1298,7 +1298,7 @@ bool QgsVectorLayer::readXml( const QDomNode& layer_node )
|
||||
//load vector joins
|
||||
if ( !mJoinBuffer )
|
||||
{
|
||||
mJoinBuffer = new QgsVectorLayerJoinBuffer();
|
||||
mJoinBuffer = new QgsVectorLayerJoinBuffer( this );
|
||||
connect( mJoinBuffer, SIGNAL( joinedFieldsChanged() ), this, SLOT( onJoinedFieldsChanged() ) );
|
||||
}
|
||||
mJoinBuffer->readXml( layer_node );
|
||||
@ -1361,7 +1361,7 @@ bool QgsVectorLayer::setDataProvider( QString const & provider )
|
||||
// get and store the feature type
|
||||
mWkbType = mDataProvider->geometryType();
|
||||
|
||||
mJoinBuffer = new QgsVectorLayerJoinBuffer();
|
||||
mJoinBuffer = new QgsVectorLayerJoinBuffer( this );
|
||||
connect( mJoinBuffer, SIGNAL( joinedFieldsChanged() ), this, SLOT( onJoinedFieldsChanged() ) );
|
||||
mExpressionFieldBuffer = new QgsExpressionFieldBuffer();
|
||||
updateFields();
|
||||
@ -2736,10 +2736,9 @@ int QgsVectorLayer::fieldNameIndex( const QString& fieldName ) const
|
||||
return pendingFields().fieldNameIndex( fieldName );
|
||||
}
|
||||
|
||||
void QgsVectorLayer::addJoin( const QgsVectorJoinInfo& joinInfo )
|
||||
bool QgsVectorLayer::addJoin( const QgsVectorJoinInfo& joinInfo )
|
||||
{
|
||||
mJoinBuffer->addJoin( joinInfo );
|
||||
updateFields();
|
||||
return mJoinBuffer->addJoin( joinInfo );
|
||||
}
|
||||
|
||||
void QgsVectorLayer::checkJoinLayerRemove( QString theLayerId )
|
||||
@ -2750,7 +2749,6 @@ void QgsVectorLayer::checkJoinLayerRemove( QString theLayerId )
|
||||
void QgsVectorLayer::removeJoin( const QString& joinLayerId )
|
||||
{
|
||||
mJoinBuffer->removeJoin( joinLayerId );
|
||||
updateFields();
|
||||
}
|
||||
|
||||
const QList< QgsVectorJoinInfo >& QgsVectorLayer::vectorJoins() const
|
||||
|
@ -640,8 +640,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer
|
||||
|
||||
/** Joins another vector layer to this layer
|
||||
@param joinInfo join object containing join layer id, target and source field
|
||||
@note added in 1.7 */
|
||||
void addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
@note added in 1.7
|
||||
@note since 2.6 returns bool indicating whether the join can be added */
|
||||
bool addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
|
||||
/** Removes a vector layer join
|
||||
@note added in 1.7 */
|
||||
|
@ -22,7 +22,8 @@
|
||||
|
||||
#include <QDomElement>
|
||||
|
||||
QgsVectorLayerJoinBuffer::QgsVectorLayerJoinBuffer()
|
||||
QgsVectorLayerJoinBuffer::QgsVectorLayerJoinBuffer( QgsVectorLayer* layer )
|
||||
: mLayer( layer )
|
||||
{
|
||||
}
|
||||
|
||||
@ -30,10 +31,49 @@ QgsVectorLayerJoinBuffer::~QgsVectorLayerJoinBuffer()
|
||||
{
|
||||
}
|
||||
|
||||
void QgsVectorLayerJoinBuffer::addJoin( const QgsVectorJoinInfo& joinInfo )
|
||||
static QList<QgsVectorLayer*> _outEdges( QgsVectorLayer* vl )
|
||||
{
|
||||
QList<QgsVectorLayer*> lst;
|
||||
foreach ( const QgsVectorJoinInfo& info, vl->vectorJoins() )
|
||||
{
|
||||
if ( QgsVectorLayer* joinVl = qobject_cast<QgsVectorLayer*>( QgsMapLayerRegistry::instance()->mapLayer( info.joinLayerId ) ) )
|
||||
lst << joinVl;
|
||||
}
|
||||
return lst;
|
||||
}
|
||||
|
||||
static bool _hasCycleDFS( QgsVectorLayer* n, QHash<QgsVectorLayer*, int>& mark )
|
||||
{
|
||||
if ( mark.value( n ) == 1 ) // temporary
|
||||
return true;
|
||||
if ( mark.value( n ) == 0 ) // not visited
|
||||
{
|
||||
mark[n] = 1; // temporary
|
||||
foreach ( QgsVectorLayer* m, _outEdges(n) )
|
||||
{
|
||||
if ( _hasCycleDFS( m, mark ) )
|
||||
return true;
|
||||
}
|
||||
mark[n] = 2; // permanent
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool QgsVectorLayerJoinBuffer::addJoin( const QgsVectorJoinInfo& joinInfo )
|
||||
{
|
||||
mVectorJoins.push_back( joinInfo );
|
||||
|
||||
// run depth-first search to detect cycles in the graph of joins between layers.
|
||||
// any cycle would cause infinite recursion when updating fields
|
||||
QHash<QgsVectorLayer*, int> markDFS;
|
||||
if ( mLayer && _hasCycleDFS( mLayer, markDFS ) )
|
||||
{
|
||||
// we have to reject this one
|
||||
mVectorJoins.pop_back();
|
||||
return false;
|
||||
}
|
||||
|
||||
//cache joined layer to virtual memory if specified by user
|
||||
if ( joinInfo.memoryCache )
|
||||
{
|
||||
@ -48,6 +88,7 @@ void QgsVectorLayerJoinBuffer::addJoin( const QgsVectorJoinInfo& joinInfo )
|
||||
connect( vl, SIGNAL( updatedFields() ), this, SLOT( joinedLayerUpdatedFields() ), Qt::UniqueConnection );
|
||||
|
||||
emit joinedFieldsChanged();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -315,7 +356,7 @@ const QgsVectorJoinInfo* QgsVectorLayerJoinBuffer::joinForFieldIndex( int index,
|
||||
|
||||
QgsVectorLayerJoinBuffer* QgsVectorLayerJoinBuffer::clone() const
|
||||
{
|
||||
QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer;
|
||||
QgsVectorLayerJoinBuffer* cloned = new QgsVectorLayerJoinBuffer( mLayer );
|
||||
cloned->mVectorJoins = mVectorJoins;
|
||||
return cloned;
|
||||
}
|
||||
|
@ -33,12 +33,13 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QgsVectorLayerJoinBuffer();
|
||||
QgsVectorLayerJoinBuffer( QgsVectorLayer* layer = 0 );
|
||||
~QgsVectorLayerJoinBuffer();
|
||||
|
||||
/**Joins another vector layer to this layer
|
||||
@param joinInfo join object containing join layer id, target and source field */
|
||||
void addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
@param joinInfo join object containing join layer id, target and source field
|
||||
@return (since 2.6) whether the join was successfully added */
|
||||
bool addJoin( const QgsVectorJoinInfo& joinInfo );
|
||||
|
||||
/**Removes a vector layer join*/
|
||||
void removeJoin( const QString& joinLayerId );
|
||||
@ -90,6 +91,8 @@ class CORE_EXPORT QgsVectorLayerJoinBuffer : public QObject
|
||||
|
||||
private:
|
||||
|
||||
QgsVectorLayer* mLayer;
|
||||
|
||||
/**Joined vector layers*/
|
||||
QgsVectorJoinList mVectorJoins;
|
||||
|
||||
|
@ -42,6 +42,7 @@ class TestVectorLayerJoinBuffer: public QObject
|
||||
void testJoinBasic_data();
|
||||
void testJoinBasic();
|
||||
void testJoinTransitive();
|
||||
void testJoinDetectCycle();
|
||||
void testJoinSubset_data();
|
||||
void testJoinSubset();
|
||||
|
||||
@ -201,6 +202,33 @@ void TestVectorLayerJoinBuffer::testJoinTransitive()
|
||||
}
|
||||
|
||||
|
||||
void TestVectorLayerJoinBuffer::testJoinDetectCycle()
|
||||
{
|
||||
// if A joins B and B joins A, we may get to an infinite loop if the case is not handled properly
|
||||
|
||||
QgsVectorJoinInfo joinInfo;
|
||||
joinInfo.targetFieldName = "id_a";
|
||||
joinInfo.joinLayerId = mLayerB->id();
|
||||
joinInfo.joinFieldName = "id_b";
|
||||
joinInfo.memoryCache = true;
|
||||
mLayerA->addJoin( joinInfo );
|
||||
|
||||
QgsVectorJoinInfo joinInfo2;
|
||||
joinInfo2.targetFieldName = "id_b";
|
||||
joinInfo2.joinLayerId = mLayerA->id();
|
||||
joinInfo2.joinFieldName = "id_a";
|
||||
joinInfo2.memoryCache = true;
|
||||
bool res = mLayerB->addJoin( joinInfo2 );
|
||||
|
||||
QVERIFY( !res );
|
||||
|
||||
// the join in layer B must be rejected
|
||||
QVERIFY( mLayerB->vectorJoins().count() == 0 );
|
||||
|
||||
mLayerA->removeJoin( mLayerB->id() );
|
||||
}
|
||||
|
||||
|
||||
void TestVectorLayerJoinBuffer::testJoinSubset_data()
|
||||
{
|
||||
QTest::addColumn<bool>( "memoryCache" );
|
||||
|
Loading…
x
Reference in New Issue
Block a user