From 850077df6525e5747c0f1cd0c29c11d47b8ef6b7 Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Sat, 18 Mar 2023 18:17:34 +0200 Subject: [PATCH 1/7] Added more detailed logs what is going on when creating deltas Seems QField sometimes does not know what is the source layer primary key OR source layer id in the deltas, so let's help QField share what is wrong. --- src/core/deltafilewrapper.cpp | 31 +++++++++++++++++++++++++++++++ src/core/layerobserver.cpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/src/core/deltafilewrapper.cpp b/src/core/deltafilewrapper.cpp index 076d209f8c..0114ecc187 100644 --- a/src/core/deltafilewrapper.cpp +++ b/src/core/deltafilewrapper.cpp @@ -650,13 +650,20 @@ void DeltaFileWrapper::addPatch( const QString &localLayerId, const QString &sou QMap layerPkDeltaIdx = mLocalPkDeltaIdx.value( localLayerId ); QString localPk = delta.value( QStringLiteral( "localPk" ) ).toString(); + qInfo() << "DeltaFileWrapper::addPatch: localPk=" << localPk << " layerPkDeltaIdx=" << layerPkDeltaIdx; + if ( layerPkDeltaIdx.contains( localPk ) ) { int deltaIdx = layerPkDeltaIdx.take( localPk ); QJsonObject deltaCreate = mDeltas.at( deltaIdx ).toObject(); + + Q_ASSERT( deltaCreate.value( QStringLiteral( "method" ) ).toString() == QStringLiteral( "create" ) ); + QJsonObject newCreate = deltaCreate.value( QStringLiteral( "new" ) ).toObject(); QJsonObject attributesCreate = newCreate.value( QStringLiteral( "attributes" ) ).toObject(); + qInfo() << "DeltaFileWrapper::addPatch: replacing an existing create delta: " << deltaCreate << "at" << deltaIdx; + if ( !newData.value( QStringLiteral( "geometry" ) ).isUndefined() ) { newCreate.insert( QStringLiteral( "geometry" ), newData.value( QStringLiteral( "geometry" ) ) ); @@ -675,12 +682,16 @@ void DeltaFileWrapper::addPatch( const QString &localLayerId, const QString &sou mDeltas.replace( deltaIdx, deltaCreate ); + qInfo() << "DeltaFileWrapper::addPatch: replaced an existing create delta: " << deltaCreate; + return; } else { mDeltas.append( delta ); + qInfo() << "DeltaFileWrapper::addPatch: Added a new patch delta: " << delta; + emit countChanged(); } } @@ -756,6 +767,8 @@ void DeltaFileWrapper::addDelete( const QString &localLayerId, const QString &so mDeltas.append( delta ); mIsDirty = true; + qInfo() << "DeltaFileWrapper::addDelete: Added a new delete delta: " << delta; + emit countChanged(); } @@ -835,6 +848,8 @@ void DeltaFileWrapper::addCreate( const QString &localLayerId, const QString &so mDeltas.append( delta ); mIsDirty = true; + qInfo() << "DeltaFileWrapper::addCreate: Added a new create delta: " << delta; + emit countChanged(); } @@ -1114,11 +1129,15 @@ QPair DeltaFileWrapper::getLocalPkAttribute( const QgsVectorLayer // we assume the first index to be the primary key index... kinda stupid, but memory layers don't have primary key at all, but we use it on geopackages, but... snap! const int pkAttrIdx = pkAttrs[0]; + qInfo() << "DeltaFileWrapper::getLocalPkAttribute: vl->primaryKeyglAttributes()=" << vl->primaryKeyAttributes() << " pkAttrs=" << pkAttrs; + if ( pkAttrIdx == -1 ) return QPair( -1, QString() ); const QString pkAttrName = fields.at( pkAttrIdx ).name(); + qInfo() << "DeltaFileWrapper::getLocalPkAttribute: pkAttrName=" << pkAttrName << " pkAttrIdx=" << pkAttrIdx; + return QPair( pkAttrIdx, pkAttrName ); } @@ -1127,11 +1146,19 @@ QPair DeltaFileWrapper::getSourcePkAttribute( const QgsVectorLayer { QString pkAttrNamesAggr = vl->customProperty( QStringLiteral( "QFieldSync/sourceDataPrimaryKeys" ) ).toString(); + qInfo() << "DeltaFileWrapper::getSourcePkAttribute: getting pkAttrNamesAggr=" << pkAttrNamesAggr << " with type=" << vl->customProperty( QStringLiteral( "QFieldSync/sourceDataPrimaryKeys" ) ).typeName(); + if ( pkAttrNamesAggr.isEmpty() ) + { + qInfo() << "DeltaFileWrapper::getSourcePkAttribute: empty pkAttrNamesAggr, gotcha!"; + return getLocalPkAttribute( vl ); + } QStringList pkAttrNames = pkAttrNamesAggr.split( QStringLiteral( "," ) ); + qInfo() << "DeltaFileWrapper::getSourcePkAttribute: pk attrs pkAttrNames=" << pkAttrNames.size(); + if ( pkAttrNames.size() > 1 ) return QPair( -1, QString() ); @@ -1139,6 +1166,8 @@ QPair DeltaFileWrapper::getSourcePkAttribute( const QgsVectorLayer const QgsFields fields = vl->fields(); const int pkAttrIdx = fields.indexFromName( pkAttrName ); + qInfo() << "DeltaFileWrapper::getSourcePkAttribute: pk pkAttrName=" << pkAttrName << " with index=" << pkAttrIdx; + if ( pkAttrIdx == -1 ) return QPair( -1, QString() ); @@ -1147,5 +1176,7 @@ QPair DeltaFileWrapper::getSourcePkAttribute( const QgsVectorLayer QString DeltaFileWrapper::getSourceLayerId( const QgsVectorLayer *vl ) { + qInfo() << "DeltaFileWrapper::getSourceLayerId: remoteLayerId=" << ( vl ? vl->customProperty( QStringLiteral( "remoteLayerId" ) ).toString() : QString() ); + return vl ? vl->customProperty( QStringLiteral( "remoteLayerId" ) ).toString() : QString(); } diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index 97f98567ec..edf7cd297e 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -25,6 +25,8 @@ #include #include +#include + LayerObserver::LayerObserver( const QgsProject *project ) : mProject( project ) @@ -113,6 +115,8 @@ void LayerObserver::onBeforeCommitChanges() while ( featuresIt.nextFeature( f ) ) changedFeatures.insert( f.id(), f ); + qInfo() << "LayerObserver::onBeforeCommitChanges: vl->id()=" << vl->id() << "sourcePkAttrPair=" << changedFids; + // NOTE no need to keep track of added features, as they are always present in the layer after commit mChangedFeatures.insert( vl->id(), changedFeatures ); mPatchedFids.insert( vl->id(), QgsFeatureIds() ); @@ -129,8 +133,12 @@ void LayerObserver::onCommittedFeaturesAdded( const QString &localLayerId, const const QPair localPkAttrPair = DeltaFileWrapper::getLocalPkAttribute( vl ); const QPair sourcePkAttrPair = DeltaFileWrapper::getSourcePkAttribute( vl ); + qInfo() << "LayerObserver::onCommittedFeaturesAdded: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " addedFeatures=" << addedFeatures; + for ( const QgsFeature &newFeature : addedFeatures ) { + qInfo() << "LayerObserver::onCommittedFeaturesAdded: adding create delta... FEATURE=" << newFeature; + mDeltaFileWrapper->addCreate( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, newFeature ); } } @@ -147,11 +155,16 @@ void LayerObserver::onCommittedFeaturesRemoved( const QString &localLayerId, con const QPair localPkAttrPair = DeltaFileWrapper::getLocalPkAttribute( vl ); const QPair sourcePkAttrPair = DeltaFileWrapper::getSourcePkAttribute( vl ); + qInfo() << "LayerObserver::onCommittedFeaturesRemoved: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " deletedFeatureIds=" << deletedFeatureIds; + for ( const QgsFeatureId &fid : deletedFeatureIds ) { Q_ASSERT( changedFeatures.contains( fid ) ); QgsFeature oldFeature = changedFeatures.take( fid ); + + qInfo() << "LayerObserver::onCommittedFeaturesRemoved: adding delete delta... FID=" << fid; + mDeltaFileWrapper->addDelete( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature ); } @@ -172,6 +185,8 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer const QPair localPkAttrPair = DeltaFileWrapper::getLocalPkAttribute( vl ); const QPair sourcePkAttrPair = DeltaFileWrapper::getSourcePkAttribute( vl ); + qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " changedAttributesValuesFids=" << changedAttributesValuesFids; + for ( const QgsFeatureId &fid : changedAttributesValuesFids ) { if ( patchedFids.contains( fid ) ) @@ -183,6 +198,9 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer QgsFeature oldFeature = changedFeatures.take( fid ); QgsFeature newFeature = vl->getFeature( fid ); + + qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: adding patch delta... FID=" << fid; + mDeltaFileWrapper->addPatch( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature, newFeature ); } @@ -204,6 +222,8 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c const QPair localPkAttrPair = DeltaFileWrapper::getLocalPkAttribute( vl ); const QPair sourcePkAttrPair = DeltaFileWrapper::getSourcePkAttribute( vl ); + qInfo() << "LayerObserver::onCommittedGeometriesChanges: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " changedGeometriesFids=" << changedGeometriesFids; + for ( const QgsFeatureId &fid : changedGeometriesFids ) { if ( patchedFids.contains( fid ) ) @@ -216,6 +236,8 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c QgsFeature oldFeature = changedFeatures.take( fid ); QgsFeature newFeature = vl->getFeature( fid ); + qInfo() << "LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; + mDeltaFileWrapper->addPatch( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature, newFeature ); } @@ -237,6 +259,9 @@ void LayerObserver::onEditingStopped() // TODO somehow indicate the user that writing failed QgsMessageLog::logMessage( QStringLiteral( "Failed writing JSON file" ) ); } + + AppInterface::instance()->sendLog( QStringLiteral( "Called LayerObserver::onEditingStopped!" ) ); + emit layerEdited( layerId ); } @@ -273,6 +298,11 @@ void LayerObserver::addLayerListeners() continue; } + qInfo() << "LayerObserver::addLayerListeners: vl->getLocalPkAttribute()=" << DeltaFileWrapper::getLocalPkAttribute( vl ); + qInfo() << "LayerObserver::addLayerListeners: vl->getSourcePkAttribute()=" << DeltaFileWrapper::getSourcePkAttribute( vl ); + qInfo() << "LayerObserver::addLayerListeners: vl->customProperties()=" << vl->customProperties().keys(); + qInfo() << "LayerObserver::addLayerListeners: vl->originalXmlProperties()=" << vl->originalXmlProperties(); + disconnect( vl, &QgsVectorLayer::beforeCommitChanges, this, &LayerObserver::onBeforeCommitChanges ); disconnect( vl, &QgsVectorLayer::committedFeaturesAdded, this, &LayerObserver::onCommittedFeaturesAdded ); disconnect( vl, &QgsVectorLayer::committedFeaturesRemoved, this, &LayerObserver::onCommittedFeaturesRemoved ); From 7c5030a998f6a4a8c4f17a4e11e777e47d1fecbf Mon Sep 17 00:00:00 2001 From: Mathieu Pellerin Date: Sat, 25 Mar 2023 17:30:54 +0700 Subject: [PATCH 2/7] Why is Qt6 failing, include missing? --- src/core/deltafilewrapper.cpp | 1 + src/core/layerobserver.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/core/deltafilewrapper.cpp b/src/core/deltafilewrapper.cpp index 0114ecc187..a3468a7971 100644 --- a/src/core/deltafilewrapper.cpp +++ b/src/core/deltafilewrapper.cpp @@ -20,6 +20,7 @@ #include "utils/fileutils.h" #include "utils/qfieldcloudutils.h" +#include #include #include #include diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index edf7cd297e..d5e0549315 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -18,6 +18,7 @@ #include "layerobserver.h" #include "qfieldcloudutils.h" +#include #include #include #include From aadae780651da05ddedaa2c63b197a74e019b377 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 25 Mar 2023 12:39:01 +0100 Subject: [PATCH 3/7] Fix build on Qt6 --- src/core/layerobserver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index d5e0549315..288291eccd 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -134,11 +134,11 @@ void LayerObserver::onCommittedFeaturesAdded( const QString &localLayerId, const const QPair localPkAttrPair = DeltaFileWrapper::getLocalPkAttribute( vl ); const QPair sourcePkAttrPair = DeltaFileWrapper::getSourcePkAttribute( vl ); - qInfo() << "LayerObserver::onCommittedFeaturesAdded: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " addedFeatures=" << addedFeatures; + qInfo() << "LayerObserver::onCommittedFeaturesAdded: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId; for ( const QgsFeature &newFeature : addedFeatures ) { - qInfo() << "LayerObserver::onCommittedFeaturesAdded: adding create delta... FEATURE=" << newFeature; + qInfo() << " LayerObserver::onCommittedFeaturesAdded: adding create delta... FEATURE=" << newFeature; mDeltaFileWrapper->addCreate( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, newFeature ); } From a56a047ef3446e5174e3dd1dab104683aeaddf53 Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 25 Mar 2023 13:14:00 +0100 Subject: [PATCH 4/7] Spacing --- src/core/layerobserver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index 288291eccd..72b53223d5 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -188,7 +188,7 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: sourcePkAttrPair=" << sourcePkAttrPair << " sourceLayerId=" << sourceLayerId << " changedAttributesValuesFids=" << changedAttributesValuesFids; - for ( const QgsFeatureId &fid : changedAttributesValuesFids ) + for ( const QgsFeatureId fid : changedAttributesValuesFids ) { if ( patchedFids.contains( fid ) ) continue; @@ -237,7 +237,7 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c QgsFeature oldFeature = changedFeatures.take( fid ); QgsFeature newFeature = vl->getFeature( fid ); - qInfo() << "LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; + qInfo() << " LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; mDeltaFileWrapper->addPatch( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature, newFeature ); } From 1efe60b2b818cf3a4d57c89b17bf16f267737bdd Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 25 Mar 2023 14:05:06 +0100 Subject: [PATCH 5/7] Targetted logging --- src/core/layerobserver.cpp | 14 +++++++++++++- src/core/layerobserver.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index 72b53223d5..04655bee0d 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -202,6 +202,10 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: adding patch delta... FID=" << fid; + if ( localPkAttrPair.second == sourcePkAttrPair.second ) + { + mLocalAndSourcePkAttrAreEqual = true; + } mDeltaFileWrapper->addPatch( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature, newFeature ); } @@ -239,6 +243,10 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c qInfo() << " LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; + if ( localPkAttrPair.second == sourcePkAttrPair.second ) + { + mLocalAndSourcePkAttrAreEqual = true; + } mDeltaFileWrapper->addPatch( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature, newFeature ); } @@ -261,7 +269,11 @@ void LayerObserver::onEditingStopped() QgsMessageLog::logMessage( QStringLiteral( "Failed writing JSON file" ) ); } - AppInterface::instance()->sendLog( QStringLiteral( "Called LayerObserver::onEditingStopped!" ) ); + if ( vl->source().contains( QStringLiteral( "data.gpkg" ) ) && mLocalAndSourcePkAttrAreEqual ) + { + AppInterface::instance()->sendLog( QStringLiteral( "Called LayerObserver::onEditingStopped!" ) ); + mLocalAndSourcePkAttrAreEqual = false; + } emit layerEdited( layerId ); } diff --git a/src/core/layerobserver.h b/src/core/layerobserver.h index 8478e10cc4..0f4ece2fa3 100644 --- a/src/core/layerobserver.h +++ b/src/core/layerobserver.h @@ -176,6 +176,8 @@ class LayerObserver : public QObject * Assigns listeners only for layer actions of `cloud` and `offline`. */ void addLayerListeners(); + + bool mLocalAndSourcePkAttrAreEqual = false; }; #endif // LAYEROBSERVER_H From cb94d41035b8bb75ea74273e4310d5d1d88983ea Mon Sep 17 00:00:00 2001 From: Matthias Kuhn Date: Sat, 25 Mar 2023 14:27:30 +0100 Subject: [PATCH 6/7] More debug info --- src/core/layerobserver.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index 04655bee0d..9f0166644d 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -138,7 +138,7 @@ void LayerObserver::onCommittedFeaturesAdded( const QString &localLayerId, const for ( const QgsFeature &newFeature : addedFeatures ) { - qInfo() << " LayerObserver::onCommittedFeaturesAdded: adding create delta... FEATURE=" << newFeature; + qInfo() << " LayerObserver::onCommittedFeaturesAdded: adding create delta... FID=" << newFeature.id(); mDeltaFileWrapper->addCreate( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, newFeature ); } @@ -164,7 +164,7 @@ void LayerObserver::onCommittedFeaturesRemoved( const QString &localLayerId, con QgsFeature oldFeature = changedFeatures.take( fid ); - qInfo() << "LayerObserver::onCommittedFeaturesRemoved: adding delete delta... FID=" << fid; + qInfo() << " LayerObserver::onCommittedFeaturesRemoved: adding delete delta... FID=" << fid; mDeltaFileWrapper->addDelete( localLayerId, sourceLayerId, localPkAttrPair.second, sourcePkAttrPair.second, oldFeature ); } @@ -202,7 +202,7 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: adding patch delta... FID=" << fid; - if ( localPkAttrPair.second == sourcePkAttrPair.second ) + if ( vl->fields().index( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) { mLocalAndSourcePkAttrAreEqual = true; } @@ -243,7 +243,7 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c qInfo() << " LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; - if ( localPkAttrPair.second == sourcePkAttrPair.second ) + if ( vl->fields().index( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) { mLocalAndSourcePkAttrAreEqual = true; } From 1be52e811a8003cb75c2b897b0932c8db51c0abf Mon Sep 17 00:00:00 2001 From: Ivan Ivanov Date: Sat, 25 Mar 2023 16:27:21 +0200 Subject: [PATCH 7/7] Typo `QgsFields.index` should be `QgsFields.indexOf` --- src/core/layerobserver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/layerobserver.cpp b/src/core/layerobserver.cpp index 9f0166644d..08ea91d938 100644 --- a/src/core/layerobserver.cpp +++ b/src/core/layerobserver.cpp @@ -202,7 +202,7 @@ void LayerObserver::onCommittedAttributeValuesChanges( const QString &localLayer qInfo() << "LayerObserver::onCommittedAttributeValuesChanges: adding patch delta... FID=" << fid; - if ( vl->fields().index( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) + if ( vl->fields().indexOf( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) { mLocalAndSourcePkAttrAreEqual = true; } @@ -243,7 +243,7 @@ void LayerObserver::onCommittedGeometriesChanges( const QString &localLayerId, c qInfo() << " LayerObserver::onCommittedGeometriesChanges: adding patch delta... FID=" << fid; - if ( vl->fields().index( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) + if ( vl->fields().indexOf( "fid_1" ) != -1 && localPkAttrPair.second == sourcePkAttrPair.second && newFeature.attribute( "fid" ) != newFeature.attribute( "fid_1" ) ) { mLocalAndSourcePkAttrAreEqual = true; }