More square brackets

This commit is contained in:
Nyall Dawson 2018-12-17 11:31:04 +10:00
parent 3490217209
commit 06d5f924f6
12 changed files with 299 additions and 13 deletions

View File

@ -60,7 +60,8 @@ Abstract base class for all nodes that can appear in an expression.
ntFunction,
ntLiteral,
ntColumnRef,
ntCondition
ntCondition,
ntIndexOperator,
};

View File

@ -179,6 +179,63 @@ Returns a the name of this operator without the operands.
I.e. "AND", "OR", ...
%End
};
class QgsExpressionNodeIndexOperator : QgsExpressionNode
{
%Docstring
A indexing expression operator, which allows use of square brackets [] to reference map and array items.
.. versionadded:: 3.6
%End
%TypeHeaderCode
#include "qgsexpressionnodeimpl.h"
%End
public:
QgsExpressionNodeIndexOperator( QgsExpressionNode *container /Transfer/, QgsExpressionNode *index /Transfer/ );
%Docstring
Constructor for QgsExpressionNodeIndexOperator.
%End
~QgsExpressionNodeIndexOperator();
QgsExpressionNode *container() const;
%Docstring
Returns the container node, representing an array or map value.
.. seealso:: :py:func:`index`
%End
QgsExpressionNode *index() const;
%Docstring
Returns the index node, representing an array element index or map key.
.. seealso:: :py:func:`container`
%End
virtual QgsExpressionNode::NodeType nodeType() const;
virtual bool prepareNode( QgsExpression *parent, const QgsExpressionContext *context );
virtual QVariant evalNode( QgsExpression *parent, const QgsExpressionContext *context );
virtual QString dump() const;
virtual QSet<QString> referencedColumns() const;
virtual QSet<QString> referencedVariables() const;
virtual QSet<QString> referencedFunctions() const;
virtual bool needsGeometry() const;
virtual QgsExpressionNode *clone() const /Factory/;
virtual bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const;
};
class QgsExpressionNodeInOperator : QgsExpressionNode

View File

@ -0,0 +1,15 @@
{
"name": "[]",
"type": "operator",
"description": "Index operator. Returns an element from an array or map value.",
"arguments": [
{ "arg": "index", "description": "array index or map key value" }
],
"examples": [
{ "expression":"array(1,2,3)[0]", "returns":"1"},
{ "expression":"array(1,2,3)[2]", "returns":"3"},
{ "expression":"array(1,2,3)[-1]", "returns":"3"},
{ "expression":"map('a',1,'b',2)['a']", "returns":"1"},
{ "expression":"map('a',1,'b',2)['b']", "returns":"2"}
]
}

View File

@ -79,7 +79,8 @@ class CORE_EXPORT QgsExpressionNode SIP_ABSTRACT
ntFunction, //!< \see QgsExpression::Node::NodeFunction
ntLiteral, //!< \see QgsExpression::Node::NodeLiteral
ntColumnRef, //!< \see QgsExpression::Node::NodeColumnRef
ntCondition //!< \see QgsExpression::Node::NodeCondition
ntCondition, //!< \see QgsExpression::Node::NodeCondition
ntIndexOperator, //!< Index operator
};

View File

@ -1548,3 +1548,97 @@ QString QgsExpressionNodeBinaryOperator::text() const
return BINARY_OPERATOR_TEXT[mOp];
}
//
QVariant QgsExpressionNodeIndexOperator::evalNode( QgsExpression *parent, const QgsExpressionContext *context )
{
const QVariant container = mContainer->eval( parent, context );
ENSURE_NO_EVAL_ERROR;
const QVariant index = mIndex->eval( parent, context );
ENSURE_NO_EVAL_ERROR;
switch ( container.type() )
{
case QVariant::Map:
return QgsExpressionUtils::getMapValue( container, parent ).value( index.toString() );
case QVariant::List:
case QVariant::StringList:
{
const QVariantList list = QgsExpressionUtils::getListValue( container, parent );
qlonglong pos = QgsExpressionUtils::getIntValue( index, parent );
if ( pos >= list.length() || pos < -list.length() )
{
return QVariant();
}
if ( pos < 0 )
{
// negative indices are from back of list
pos += list.length();
}
return list.at( pos );
}
default:
parent->setEvalErrorString( tr( "[] can only be used with map or array values, not %1" ).arg( QMetaType::typeName( container.type() ) ) );
return QVariant();
}
}
QgsExpressionNode::NodeType QgsExpressionNodeIndexOperator::nodeType() const
{
return ntIndexOperator;
}
bool QgsExpressionNodeIndexOperator::prepareNode( QgsExpression *parent, const QgsExpressionContext *context )
{
bool resC = mContainer->prepare( parent, context );
bool resV = mIndex->prepare( parent, context );
return resC && resV;
}
QString QgsExpressionNodeIndexOperator::dump() const
{
return QStringLiteral( "%1[%2]" ).arg( mContainer->dump(), mIndex->dump() );
}
QSet<QString> QgsExpressionNodeIndexOperator::referencedColumns() const
{
return mContainer->referencedColumns() + mIndex->referencedColumns();
}
QSet<QString> QgsExpressionNodeIndexOperator::referencedVariables() const
{
return mContainer->referencedVariables() + mIndex->referencedVariables();
}
QSet<QString> QgsExpressionNodeIndexOperator::referencedFunctions() const
{
return mContainer->referencedFunctions() + mIndex->referencedFunctions();
}
QList<const QgsExpressionNode *> QgsExpressionNodeIndexOperator::nodes() const
{
QList<const QgsExpressionNode *> lst;
lst << this;
lst += mContainer->nodes() + mIndex->nodes();
return lst;
}
bool QgsExpressionNodeIndexOperator::needsGeometry() const
{
return mContainer->needsGeometry() || mIndex->needsGeometry();
}
QgsExpressionNode *QgsExpressionNodeIndexOperator::clone() const
{
QgsExpressionNodeIndexOperator *copy = new QgsExpressionNodeIndexOperator( mContainer->clone(), mIndex->clone() );
cloneTo( copy );
return copy;
}
bool QgsExpressionNodeIndexOperator::isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const
{
return mContainer->isStatic( parent, context ) && mIndex->isStatic( parent, context );
}

View File

@ -206,6 +206,57 @@ class CORE_EXPORT QgsExpressionNodeBinaryOperator : public QgsExpressionNode
static const char *BINARY_OPERATOR_TEXT[];
};
/**
* A indexing expression operator, which allows use of square brackets [] to reference map and array items.
* \ingroup core
* \since QGIS 3.6
*/
class CORE_EXPORT QgsExpressionNodeIndexOperator : public QgsExpressionNode
{
public:
/**
* Constructor for QgsExpressionNodeIndexOperator.
*/
QgsExpressionNodeIndexOperator( QgsExpressionNode *container SIP_TRANSFER, QgsExpressionNode *index SIP_TRANSFER )
: mContainer( container )
, mIndex( index )
{}
~QgsExpressionNodeIndexOperator() override { delete mContainer; delete mIndex; }
/**
* Returns the container node, representing an array or map value.
* \see index()
*/
QgsExpressionNode *container() const { return mContainer; }
/**
* Returns the index node, representing an array element index or map key.
* \see container()
*/
QgsExpressionNode *index() const { return mIndex; }
QgsExpressionNode::NodeType nodeType() const override;
bool prepareNode( QgsExpression *parent, const QgsExpressionContext *context ) override;
QVariant evalNode( QgsExpression *parent, const QgsExpressionContext *context ) override;
QString dump() const override;
QSet<QString> referencedColumns() const override;
QSet<QString> referencedVariables() const override;
QSet<QString> referencedFunctions() const override;
QList<const QgsExpressionNode *> nodes( ) const override; SIP_SKIP
bool needsGeometry() const override;
QgsExpressionNode *clone() const override SIP_FACTORY;
bool isStatic( QgsExpression *parent, const QgsExpressionContext *context ) const override;
private:
QgsExpressionNode *mContainer = nullptr;
QgsExpressionNode *mIndex = nullptr;
};
/**
* An expression node for value IN or NOT IN clauses.
* \ingroup core

View File

@ -115,7 +115,7 @@ void addParserLocation(YYLTYPE* yyloc, QgsExpressionNode *node)
//
// operator tokens
%token <b_op> OR AND EQ NE LE GE LT GT REGEXP LIKE IS PLUS MINUS MUL DIV INTDIV MOD CONCAT POW SQUARE_BRAKET_OPENING SQUARE_BRAKET_CLOSING
%token <b_op> OR AND EQ NE LE GE LT GT REGEXP LIKE IS PLUS MINUS MUL DIV INTDIV MOD CONCAT POW
%token <u_op> NOT
%token IN
@ -166,6 +166,7 @@ void addParserLocation(YYLTYPE* yyloc, QgsExpressionNode *node)
%right UMINUS // fictitious symbol (for unary minus)
%left COMMA
%left '['
%destructor { delete $$; } <node>
%destructor { delete $$; } <nodelist>
@ -284,15 +285,8 @@ expression:
| expression IN '(' exp_list ')' { $$ = new QgsExpressionNodeInOperator($1, $4, false); }
| expression NOT IN '(' exp_list ')' { $$ = new QgsExpressionNodeInOperator($1, $5, true); }
| expression '[' expression ']'
{
QgsExpressionNode::NodeList* args = new QgsExpressionNode::NodeList();
args->append( $1 );
args->append( $3 );
QgsExpressionNodeFunction *f = new QgsExpressionNodeFunction( QgsExpression::functionIndex( "map_get" ), args );
$$ = f;
}
| expression '[' expression ']' { $$ = new QgsExpressionNodeIndexOperator( $1, $3 ); }
| PLUS expression %prec UMINUS { $$ = $2; }
| MINUS expression %prec UMINUS { $$ = new QgsExpressionNodeUnaryOperator( QgsExpressionNodeUnaryOperator::uoMinus, $2); }

View File

@ -401,6 +401,9 @@ QgsSqlExpressionCompiler::Result QgsSqlExpressionCompiler::compileNode( const Qg
case QgsExpressionNode::ntCondition:
break;
case QgsExpressionNode::ntIndexOperator:
break;
}
return Fail;

View File

@ -97,7 +97,8 @@ QgsExpressionBuilderWidget::QgsExpressionBuilderWidget( QWidget *parent )
connect( mShowHelpButton, &QPushButton::clicked, this, [ = ]()
{
functionsplit->setSizes( QList<int>( {mOperationListGroup->width() - mHelpAndValuesWidget->minimumWidth(),
mHelpAndValuesWidget->minimumWidth()} ) );
mHelpAndValuesWidget->minimumWidth()
} ) );
mShowHelpButton->setEnabled( false );
} );
connect( functionsplit, &QSplitter::splitterMoved, this, [ = ]( int, int )
@ -608,6 +609,7 @@ void QgsExpressionBuilderWidget::updateFunctionTree()
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<>" ), QStringLiteral( " <> " ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "<=" ), QStringLiteral( " <= " ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( ">=" ), QStringLiteral( " >= " ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "[]" ), QStringLiteral( "[ ]" ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "||" ), QStringLiteral( " || " ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "IN" ), QStringLiteral( " IN " ) );
registerItem( QStringLiteral( "Operators" ), QStringLiteral( "LIKE" ), QStringLiteral( " LIKE " ) );
@ -957,6 +959,10 @@ void QgsExpressionBuilderWidget::createMarkers( const QgsExpressionNode *inNode
}
break;
}
case QgsExpressionNode::NodeType::ntIndexOperator:
{
break;
}
}
}

View File

@ -79,6 +79,7 @@ QgsSqlExpressionCompiler::Result QgsOgrExpressionCompiler::compileNode( const Qg
case QgsExpressionNode::ntColumnRef:
case QgsExpressionNode::ntInOperator:
case QgsExpressionNode::ntLiteral:
case QgsExpressionNode::ntIndexOperator:
break;
}

View File

@ -3181,6 +3181,69 @@ class TestQgsExpression: public QObject
QCOMPARE( v.toDateTime().toMSecsSinceEpoch(), v2.toDateTime().toMSecsSinceEpoch() );
}
void test_IndexOperator()
{
QgsExpressionContext context;
QgsExpression e( QStringLiteral( "'['" ) );
QVariant result = e.evaluate( &context );
QCOMPARE( result.toString(), QStringLiteral( "[" ) );
e = QgsExpression( QStringLiteral( "']'" ) );
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "]" ) );
e = QgsExpression( QStringLiteral( "'[3]'" ) );
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "[3]" ) );
e = QgsExpression( QStringLiteral( "'a[3]'" ) );
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "a[3]" ) );
e = QgsExpression( QStringLiteral( "\"a[3]\"" ) );
QCOMPARE( e.evaluate( &context ).toString(), QStringLiteral( "[a[3]]" ) );
e = QgsExpression( QStringLiteral( "(1+2)[0]" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( e.hasEvalError() );
e = QgsExpression( QStringLiteral( "(1+2)['a']" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( e.hasEvalError() );
// arrays
e = QgsExpression( QStringLiteral( "array(1,2,3)[0]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
e = QgsExpression( QStringLiteral( "((array(1,2,3)))[0]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[1]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[2]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[-1]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[-2]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[-3]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[1+1]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[(3-2)]" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
e = QgsExpression( QStringLiteral( "array(1,2,3)[3]" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
e = QgsExpression( QStringLiteral( "array(1,2,3)[-4]" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
// maps
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)[0]" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['d']" ) );
QVERIFY( !e.evaluate( &context ).isValid() );
QVERIFY( !e.hasEvalError() ); // no eval error - we are tolerant to this
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['a']" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 1 );
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['b']" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
e = QgsExpression( QStringLiteral( "map('a',1,'b',2,'c',3)['c']" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 3 );
e = QgsExpression( QStringLiteral( "map('a',1,'bbb',2,'c',3)['b'||'b'||'b']" ) );
QCOMPARE( e.evaluate( &context ).toInt(), 2 );
}
};
QGSTEST_MAIN( TestQgsExpression )

Binary file not shown.