Skip to content
Permalink
Browse files
[FEATURE] Expose @parent variable in aggregate functions
This makes it possible to access attributes and geometry from the parent
feature when in the filter of the "aggregate" expression function.

With this in place aggregates can be calculated per feature.

E.g. max "measurement" for each point_station per polygon_research_area.

Or a default attribute value when digitizing features:

    aggregate(layer:='countries', aggregate:='max', expression:=\"code\",
filter:=intersects( $geometry, geometry(@parent) ) )
  • Loading branch information
m-kuhn committed Oct 27, 2016
1 parent fb45781 commit f80a33be659200c1497a55c455a5521dc1e8c529
Showing with 159 additions and 15 deletions.
  1. +20 −2 python/core/qgsexpression.sip
  2. +100 −8 src/core/qgsexpression.cpp
  3. +24 −5 src/core/qgsexpression.h
  4. +13 −0 src/core/qgsfeature.cpp
  5. +2 −0 src/core/qgsfeature.h
@@ -45,14 +45,20 @@ class QgsExpression

/**
* Get list of columns referenced by the expression.
* @note if the returned list contains the QgsFeatureRequest::AllAttributes constant then
*
* @note If the returned list contains the QgsFeatureRequest::AllAttributes constant then
* all attributes from the layer are required for evaluation of the expression.
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
*
* TODO QGIS3: Return QSet<QString>
* @see referencedAttributeIndexes()
*/
QSet<QString> referencedColumns() const;

/**
* Return a list of all variables which are used in this expression.
*/
QSet<QString> referencedVariables() const;

/**
* Return a list of field name indexes obtained from the provided fields.
*
@@ -560,6 +566,11 @@ class QgsExpression
*/
virtual QSet<QString> referencedColumns() const = 0;

/**
* Return a list of all variables which are used in this expression.
*/
virtual QSet<QString> referencedVariables() const = 0;

/**
* Abstract virtual method which returns if the geometry is required to evaluate
* this expression.
@@ -638,6 +649,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
virtual QgsExpression::Node* clone() const;
};
@@ -658,6 +670,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
virtual QgsExpression::Node* clone() const;

@@ -692,6 +705,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
virtual QgsExpression::Node* clone() const;
};
@@ -712,6 +726,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
virtual QgsExpression::Node* clone() const;

@@ -734,6 +749,7 @@ class QgsExpression
virtual QgsExpression::Node* clone() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
};

@@ -751,6 +767,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;

virtual QgsExpression::Node* clone() const;
@@ -783,6 +800,7 @@ class QgsExpression
virtual QString dump() const;

virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual bool needsGeometry() const;
virtual QgsExpression::Node* clone() const;
};
@@ -622,7 +622,7 @@ static QVariant fcnMin( const QVariantList& values, const QgsExpressionContext*,
return QVariant( minVal );
}

static QVariant fcnAggregate( const QVariantList& values, const QgsExpressionContext* context, QgsExpression *parent )
static QVariant fcnAggregate( const QVariantList& values, const QgsExpressionContext* context, QgsExpression* parent )
{
//lazy eval, so we need to evaluate nodes now

@@ -677,15 +677,28 @@ static QVariant fcnAggregate( const QVariantList& values, const QgsExpressionCon
parameters.delimiter = value.toString();
}

QString cacheKey = QStringLiteral( "aggfcn:%1:%2:%3:%4" ).arg( vl->id(), QString::number( static_cast< int >( aggregate ) ), subExpression, parameters.filter );
if ( context && context->hasCachedValue( cacheKey ) )
return context->cachedValue( cacheKey );

QVariant result;
if ( context )
{
QString cacheKey = QStringLiteral( "aggfcn:%1:%2:%3:%4" ).arg( vl->id(), QString::number( aggregate ), subExpression, parameters.filter );

QgsExpression subExp( subExpression );
if ( subExp.referencedVariables().contains( "parent" ) || subExp.referencedVariables().contains( QString() ) )
{
cacheKey += ':' + qHash( context->feature() );
}

if ( context && context->hasCachedValue( cacheKey ) )
return context->cachedValue( cacheKey );

QgsExpressionContext subContext( *context );
QgsExpressionContextScope* subScope = new QgsExpressionContextScope();
subScope->setVariable( "parent", context->feature() );
subContext.appendScope( subScope );
result = vl->aggregate( aggregate, subExpression, parameters, &subContext, &ok );

if ( ok )
context->setCachedValue( cacheKey, result );
}
else
{
@@ -697,9 +710,6 @@ static QVariant fcnAggregate( const QVariantList& values, const QgsExpressionCon
return QVariant();
}

// cache value
if ( context )
context->setCachedValue( cacheKey, result );
return result;
}

@@ -3991,6 +4001,14 @@ QSet<QString> QgsExpression::referencedColumns() const
return d->mRootNode->referencedColumns();
}

QSet<QString> QgsExpression::referencedVariables() const
{
if ( !d->mRootNode )
return QSet<QString>();

return d->mRootNode->referencedVariables();
}

bool QgsExpression::NodeInOperator::needsGeometry() const
{
bool needs = false;
@@ -4303,6 +4321,16 @@ QString QgsExpression::NodeUnaryOperator::dump() const
return QStringLiteral( "%1 %2" ).arg( UnaryOperatorText[mOp], mOperand->dump() );
}

QSet<QString> QgsExpression::NodeUnaryOperator::referencedColumns() const
{
return mOperand->referencedColumns();
}

QSet<QString> QgsExpression::NodeUnaryOperator::referencedVariables() const
{
return mOperand->referencedVariables();
}

QgsExpression::Node*QgsExpression::NodeUnaryOperator::clone() const
{
return new NodeUnaryOperator( mOp, mOperand->clone() );
@@ -4790,6 +4818,11 @@ QSet<QString> QgsExpression::NodeBinaryOperator::referencedColumns() const
return mOpLeft->referencedColumns() + mOpRight->referencedColumns();
}

QSet<QString> QgsExpression::NodeBinaryOperator::referencedVariables() const
{
return mOpLeft->referencedVariables() + mOpRight->referencedVariables();
}

bool QgsExpression::NodeBinaryOperator::needsGeometry() const
{
return mOpLeft->needsGeometry() || mOpRight->needsGeometry();
@@ -4993,6 +5026,23 @@ QSet<QString> QgsExpression::NodeFunction::referencedColumns() const
return functionColumns;
}

QSet<QString> QgsExpression::NodeFunction::referencedVariables() const
{
Function* fd = Functions()[mFnIndex];
if ( fd->name() == "var" )
{
if ( !mArgs->list().isEmpty() )
{
QgsExpression::NodeLiteral* var = dynamic_cast<QgsExpression::NodeLiteral*>( mArgs->list().first() );
if ( var )
return QSet<QString>() << var->value().toString();
}
return QSet<QString>() << QString();
}
else
return QSet<QString>();
}

bool QgsExpression::NodeFunction::needsGeometry() const
{
bool needs = Functions()[mFnIndex]->usesGeometry();
@@ -5119,6 +5169,16 @@ QString QgsExpression::NodeLiteral::dump() const
}
}

QSet<QString> QgsExpression::NodeLiteral::referencedColumns() const
{
return QSet<QString>();
}

QSet<QString> QgsExpression::NodeLiteral::referencedVariables() const
{
return QSet<QString>();
}

QgsExpression::Node*QgsExpression::NodeLiteral::clone() const
{
return new NodeLiteral( mValue );
@@ -5177,6 +5237,16 @@ QString QgsExpression::NodeColumnRef::dump() const
return QRegExp( "^[A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*$" ).exactMatch( mName ) ? mName : quotedColumnRef( mName );
}

QSet<QString> QgsExpression::NodeColumnRef::referencedColumns() const
{
return QSet<QString>() << mName;
}

QSet<QString> QgsExpression::NodeColumnRef::referencedVariables() const
{
return QSet<QString>();
}

QgsExpression::Node*QgsExpression::NodeColumnRef::clone() const
{
return new NodeColumnRef( mName );
@@ -5253,6 +5323,20 @@ QSet<QString> QgsExpression::NodeCondition::referencedColumns() const
return lst;
}

QSet<QString> QgsExpression::NodeCondition::referencedVariables() const
{
QSet<QString> lst;
Q_FOREACH ( WhenThen* cond, mConditions )
{
lst += cond->mWhenExp->referencedVariables() + cond->mThenExp->referencedVariables();
}

if ( mElseExp )
lst += mElseExp->referencedVariables();

return lst;
}

bool QgsExpression::NodeCondition::needsGeometry() const
{
Q_FOREACH ( WhenThen* cond, mConditions )
@@ -5619,6 +5703,14 @@ QSet<QString> QgsExpression::NodeInOperator::referencedColumns() const
return lst;
}

QSet<QString> QgsExpression::NodeInOperator::referencedVariables() const
{
QSet<QString> lst( mNode->referencedVariables() );
Q_FOREACH ( const Node* n, mList->list() )
lst.unite( n->referencedVariables() );
return lst;
}

bool QgsExpression::Function::operator==( const QgsExpression::Function& other ) const
{
if ( QString::compare( mName, other.mName, Qt::CaseInsensitive ) == 0 )
@@ -180,11 +180,18 @@ class CORE_EXPORT QgsExpression
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
*
* @see referencedAttributeIndexes()
*
* TODO QGIS3: Return QSet<QString>
*/
QSet<QString> referencedColumns() const;

/**
* Return a list of all variables which are used in this expression.
* If the list contains a NULL QString, there is a variable name used
* which is determined at runtime.
*
* @note Added in QGIS 3.0
*/
QSet<QString> referencedVariables() const;

/**
* Return a list of field name indexes obtained from the provided fields.
*
@@ -857,6 +864,11 @@ class CORE_EXPORT QgsExpression
*/
virtual QSet<QString> referencedColumns() const = 0;

/**
* Return a list of all variables which are used in this expression.
*/
virtual QSet<QString> referencedVariables() const = 0;

/**
* Abstract virtual method which returns if the geometry is required to evaluate
* this expression.
@@ -953,7 +965,8 @@ class CORE_EXPORT QgsExpression
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override { return mOperand->referencedColumns(); }
virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override { return mOperand->needsGeometry(); }
virtual Node* clone() const override;

@@ -984,6 +997,7 @@ class CORE_EXPORT QgsExpression
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override;
virtual Node* clone() const override;

@@ -1028,6 +1042,7 @@ class CORE_EXPORT QgsExpression
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override;
virtual Node* clone() const override;

@@ -1055,6 +1070,7 @@ class CORE_EXPORT QgsExpression
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override;
virtual Node* clone() const override;

@@ -1084,7 +1100,8 @@ class CORE_EXPORT QgsExpression
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override { return QSet<QString>(); }
virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override { return false; }
virtual Node* clone() const override;

@@ -1110,7 +1127,8 @@ class CORE_EXPORT QgsExpression
virtual QVariant eval( QgsExpression* parent, const QgsExpressionContext* context ) override;
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override { return QSet<QString>() << mName; }
virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override { return false; }

virtual Node* clone() const override;
@@ -1162,6 +1180,7 @@ class CORE_EXPORT QgsExpression
virtual QString dump() const override;

virtual QSet<QString> referencedColumns() const override;
virtual QSet<QString> referencedVariables() const override;
virtual bool needsGeometry() const override;
virtual Node* clone() const override;

@@ -313,3 +313,16 @@ QDataStream& operator>>( QDataStream& in, QgsFeature& feature )
feature.setValid( valid );
return in;
}

uint qHash( const QgsFeature& key, uint seed )
{
uint hash = seed;
Q_FOREACH ( const QVariant& attr, key.attributes() )
{
hash ^= qHash( attr.toString() );
}

hash ^= qHash( key.geometry().exportToWkt() );

return hash;
}

1 comment on commit f80a33b

@andreasneumann

This comment has been minimized.

Copy link
Member

@andreasneumann andreasneumann commented on f80a33b Oct 28, 2016

Nice! The expressions are more and more powerful! Thanks for the improvement.

Please sign in to comment.