Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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 f80a33b
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 15 deletions.
22 changes: 20 additions & 2 deletions python/core/qgsexpression.sip
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
};
Expand All @@ -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;

Expand Down Expand Up @@ -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;
};
Expand All @@ -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;

Expand All @@ -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;
};

Expand All @@ -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;
Expand Down Expand Up @@ -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;
};
Expand Down
108 changes: 100 additions & 8 deletions src/core/qgsexpression.cpp
Expand Up @@ -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

Expand Down Expand Up @@ -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
{
Expand All @@ -697,9 +710,6 @@ static QVariant fcnAggregate( const QVariantList& values, const QgsExpressionCon
return QVariant();
}

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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 );
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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 )
Expand Down
29 changes: 24 additions & 5 deletions src/core/qgsexpression.h
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;

Expand All @@ -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;
Expand Down Expand Up @@ -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;

Expand Down
13 changes: 13 additions & 0 deletions src/core/qgsfeature.cpp
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Please sign in to comment.