Skip to content

Commit

Permalink
Expression functions can dynamically report geometry and columns
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kuhn committed Oct 27, 2016
1 parent adb184e commit 0b3646b
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 50 deletions.
12 changes: 8 additions & 4 deletions doc/api_break.dox
Expand Up @@ -765,9 +765,6 @@ version instead.
removed. Use QgsExpressionContext variables instead.
- The replaceExpressionText version which accepts a QgsFeature argument has been removed. Use the QgsExpressionContext
version instead.
- QgsExpression::Function::func has been modified to use a QgsExpressionContext argument rather than a QgsFeature.
- QgsExpression::Function::usesgeometry() has been renamed to usesGeometry()
- QgsExpression::Function::helptext() has been renamed to helpText()
- The QgsExpression::Node::eval and prepare versions which take a QgsFeature has been removed, use the QgsExpressionContext versions instead.
- QgsExpression::Interval has been removed. Use QgsInterval instead.
- replaceExpressionText() no longer accepts a substitution map parameter. Use expression context variables instead.
Expand All @@ -776,7 +773,14 @@ version instead.
- acceptVisitor() has been removed
- QgsExpression::referencedColumns() returns QSet<QString> instead of QStringList
- QgsExpression::Node::referencedColumns() returns QSet<QString> instead of QStringList
- QgsExpression::Function::referencedColumns() returns QSet<QString> instead of QStringList

QgsExpression::Function {#qgis_api_break_3_0_QgsExpression_Function}
-----------------------

- `QgsExpression::Function::func` has been modified to use a `QgsExpressionContext` argument rather than a `QgsFeature`.
- `QgsExpression::Function::usesgeometry()` has been renamed to `QgsExpression::Function::usesGeometry( const NodeFunction* node )`
- `QStringList QgsExpression::Function::referencedColumns()` has been changed to `QSet<QString> QgsExpression::Function::referencedColumns( const NodeFunction* node )`
- `QgsExpression::Function::helptext()` has been renamed to `helpText()`


QgsFeature {#qgis_api_break_3_0_QgsFeature}
Expand Down
12 changes: 10 additions & 2 deletions python/core/__init__.py
Expand Up @@ -69,10 +69,12 @@ def myfunc(values, *args):
"""
class QgsExpressionFunction(QgsExpression.Function):

def __init__(self, func, name, args, group, helptext='', usesgeometry=True, referencedColumns=QgsFeatureRequest.AllAttributes, expandargs=False):
QgsExpression.Function.__init__(self, name, args, group, helptext, usesgeometry, referencedColumns)
def __init__(self, func, name, args, group, helptext='', usesGeometry=True, referencedColumns=QgsFeatureRequest.AllAttributes, expandargs=False):
QgsExpression.Function.__init__(self, name, args, group, helptext)
self.function = func
self.expandargs = expandargs
self.uses_geometry = usesGeometry
self.referenced_columns = referencedColumns

def func(self, values, context, parent):
feature = None
Expand All @@ -89,6 +91,12 @@ def func(self, values, context, parent):
parent.setEvalErrorString(str(ex))
return None

def usesGeometry(self, node):
return self.uses_geometry

def referencedColumns(self, node):
return self.referenced_columns

helptemplate = string.Template("""<h3>$name function</h3><br>$doc""")
name = kwargs.get('name', function.__name__)
helptext = function.__doc__ or ''
Expand Down
34 changes: 22 additions & 12 deletions python/core/qgsexpression.sip
Expand Up @@ -306,8 +306,6 @@ class QgsExpression
int params,
const QString& group,
const QString& helpText = QString(),
bool usesGeometry = false,
const QSet<QString>& referencedColumns = QSet<QString>(),
bool lazyEval = false,
bool handlesNull = false,
bool isContextual = false );
Expand All @@ -319,8 +317,6 @@ class QgsExpression
int params,
const QStringList& groups,
const QString& helpText = QString(),
bool usesGeometry = false,
const QSet<QString>& referencedColumns = QSet<QString>(),
bool lazyEval = false,
bool handlesNull = false,
bool isContextual = false );
Expand All @@ -332,8 +328,6 @@ class QgsExpression
const QgsExpression::ParameterList& params,
const QString& group,
const QString& helpText = QString(),
bool usesGeometry = false,
const QSet<QString>& referencedColumns = QSet<QString>(),
bool lazyEval = false,
bool handlesNull = false,
bool isContextual = false );
Expand All @@ -345,8 +339,6 @@ class QgsExpression
const QgsExpression::ParameterList& params,
const QStringList& groups,
const QString& helpText = QString(),
bool usesGeometry = false,
const QSet<QString>& referencedColumns = QSet<QString>(),
bool lazyEval = false,
bool handlesNull = false,
bool isContextual = false );
Expand All @@ -366,8 +358,8 @@ class QgsExpression
*/
const QgsExpression::ParameterList& parameters() const;

/** Does this function use a geometry object. */
bool usesGeometry() const;
//! Does this function use a geometry object.
virtual bool usesGeometry( const QgsExpression::NodeFunction* node ) const;

/** Returns a list of possible aliases for the function. These include
* other permissible names for the function, eg deprecated names.
Expand All @@ -382,7 +374,15 @@ class QgsExpression
*/
bool lazyEval() const;

virtual QSet<QString> referencedColumns() const;
/**
* Returns a set of field names which are required for this function.
* May contain QgsFeatureRequest::AllAttributes to signal that all
* attributes are required.
* If in doubt this will return more fields than strictly required.
*
* @note Added in QGIS 3.0
*/
virtual QSet<QString> referencedColumns( const QgsExpression::NodeFunction* node ) const;

/** Returns whether the function is only available if provided by a QgsExpressionContext object.
* @note added in QGIS 2.12
Expand Down Expand Up @@ -622,7 +622,17 @@ class QgsExpression
//! @note added in QGIS 2.16
bool hasNamedNodes() const;

const QList<QgsExpression::Node*>& list();
/**
* Get a list of all the nodes.
*/
QList<QgsExpression::Node*> list();

/**
* Get the node at position i in the list.
*
* @note Added in QGIS 3.0
*/
QgsExpression::Node* at( int i );

//! Returns a list of names for nodes. Unnamed nodes will be indicated by an empty string in the list.
//! @note added in QGIS 2.16
Expand Down
97 changes: 91 additions & 6 deletions src/core/qgsexpression.cpp
Expand Up @@ -3600,8 +3600,56 @@ const QList<QgsExpression::Function*>& QgsExpression::Functions()
<< new StaticFunction( QStringLiteral( "to_interval" ), ParameterList() << Parameter( QStringLiteral( "value" ) ), fcnToInterval, QStringList() << QStringLiteral( "Conversions" ) << QStringLiteral( "Date and Time" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "tointerval" ) )
<< new StaticFunction( QStringLiteral( "coalesce" ), -1, fcnCoalesce, QStringLiteral( "Conditionals" ), QString(), false, QSet<QString>(), false, QStringList(), true )
<< new StaticFunction( QStringLiteral( "if" ), 3, fcnIf, QStringLiteral( "Conditionals" ), QString(), False, QSet<QString>(), true )
<< new StaticFunction( QStringLiteral( "aggregate" ), ParameterList() << Parameter( QStringLiteral( "layer" ) ) << Parameter( QStringLiteral( "aggregate" ) ) << Parameter( QStringLiteral( "expression" ) )
<< Parameter( QStringLiteral( "filter" ), true ) << Parameter( QStringLiteral( "concatenator" ), true ), fcnAggregate, QStringLiteral( "Aggregates" ), QString(), true, QSet<QString>() << QgsFeatureRequest::AllAttributes, true )

<< new StaticFunction( QStringLiteral( "aggregate" ),
ParameterList()
<< Parameter( QStringLiteral( "layer" ) )
<< Parameter( QStringLiteral( "aggregate" ) )
<< Parameter( QStringLiteral( "expression" ) )
<< Parameter( QStringLiteral( "filter" ), true )
<< Parameter( QStringLiteral( "concatenator" ), true ),
fcnAggregate,
QStringLiteral( "Aggregates" ),
QString(),
[]( const QgsExpression::NodeFunction* node )
{
// usesGeometry callback: return true if @parent variable is referenced

if ( !node )
return true;

if ( !node->args() || node->args()->count() < 4 )
return false;
else
{
QgsExpression::Node* filterNode = node->args()->at( 3 );
QSet<QString> referencedVars = filterNode->referencedVariables();
return referencedVars.contains( "parent" ) || referencedVars.contains( QString() );
}
},
[]( const QgsExpression::NodeFunction* node )
{
// referencedColumns callback: return AllAttributes if @parent variable is referenced

if ( !node )
return QSet<QString>() << QgsFeatureRequest::AllAttributes;

if ( !node->args() || node->args()->count() < 4 )
return QSet<QString>();
else
{
QgsExpression::Node* filterNode = node->args()->at( 3 );
QSet<QString> referencedVars = filterNode->referencedVariables();

if ( referencedVars.contains( "parent" ) || referencedVars.contains( QString() ) )
return QSet<QString>() << QgsFeatureRequest::AllAttributes;
else
return QSet<QString>();
}
},
true
)

<< new StaticFunction( QStringLiteral( "relation_aggregate" ), ParameterList() << Parameter( QStringLiteral( "relation" ) ) << Parameter( QStringLiteral( "aggregate" ) ) << Parameter( QStringLiteral( "expression" ) ) << Parameter( QStringLiteral( "concatenator" ), true ),
fcnAggregateRelation, QStringLiteral( "Aggregates" ), QString(), False, QSet<QString>() << QgsFeatureRequest::AllAttributes, true )

Expand Down Expand Up @@ -4330,14 +4378,14 @@ QSet<QString> QgsExpression::NodeUnaryOperator::referencedVariables() const
return mOperand->referencedVariables();
}

QgsExpression::Node*QgsExpression::NodeUnaryOperator::clone() const
QgsExpression::Node* QgsExpression::NodeUnaryOperator::clone() const
{
return new NodeUnaryOperator( mOp, mOperand->clone() );
}

//

QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression *parent, const QgsExpressionContext *context )
QVariant QgsExpression::NodeBinaryOperator::eval( QgsExpression* parent, const QgsExpressionContext* context )
{
QVariant vL = mOpLeft->eval( parent, context );
ENSURE_NO_EVAL_ERROR;
Expand Down Expand Up @@ -5009,7 +5057,7 @@ QString QgsExpression::NodeFunction::dump() const
QSet<QString> QgsExpression::NodeFunction::referencedColumns() const
{
Function* fd = Functions()[mFnIndex];
QSet<QString> functionColumns = fd->referencedColumns();
QSet<QString> functionColumns = fd->referencedColumns( this );

if ( !mArgs )
{
Expand Down Expand Up @@ -5044,7 +5092,7 @@ QSet<QString> QgsExpression::NodeFunction::referencedVariables() const

bool QgsExpression::NodeFunction::needsGeometry() const
{
bool needs = Functions()[mFnIndex]->usesGeometry();
bool needs = Functions()[mFnIndex]->usesGeometry( this );
if ( mArgs )
{
Q_FOREACH ( Node* n, mArgs->list() )
Expand Down Expand Up @@ -5710,10 +5758,47 @@ QSet<QString> QgsExpression::NodeInOperator::referencedVariables() const
return lst;
}

bool QgsExpression::Function::usesGeometry( const QgsExpression::NodeFunction* node ) const
{
Q_UNUSED( node )
return true;
}

QSet<QString> QgsExpression::Function::referencedColumns( const NodeFunction* node ) const
{
Q_UNUSED( node )
return QSet<QString>() << QgsFeatureRequest::AllAttributes;
}

bool QgsExpression::Function::operator==( const QgsExpression::Function& other ) const
{
if ( QString::compare( mName, other.mName, Qt::CaseInsensitive ) == 0 )
return true;

return false;
}

QgsExpression::StaticFunction::StaticFunction( const QString& fnname, const QgsExpression::ParameterList& params, QgsExpression::FcnEval fcn, const QString& group, const QString& helpText, std::function < bool ( const NodeFunction* node ) > usesGeometry, std::function < QSet<QString>( const NodeFunction* node ) > referencedColumns, bool lazyEval, const QStringList& aliases, bool handlesNull )
: Function( fnname, params, group, helpText, lazyEval, handlesNull )
, mFnc( fcn )
, mAliases( aliases )
, mUsesGeometryFunc( usesGeometry )
, mReferencedColumnsFunc( referencedColumns )
{
}

bool QgsExpression::StaticFunction::usesGeometry( const NodeFunction* node ) const
{
if ( mUsesGeometryFunc )
return mUsesGeometryFunc( node );
else
return mUsesGeometry;
}

QSet<QString> QgsExpression::StaticFunction::referencedColumns( const NodeFunction* node ) const
{
if ( mReferencedColumnsFunc )
return mReferencedColumnsFunc( node );
else
return mReferencedColumns;
}

0 comments on commit 0b3646b

Please sign in to comment.