diff --git a/python/core/core.sip b/python/core/core.sip index 6866f15820e..e3d9ad733be 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -127,6 +127,7 @@ %Include qgssnapper.sip %Include qgssnappingutils.sip %Include qgsspatialindex.sip +%Include qgssqlstatement.sip %Include qgsstatisticalsummary.sip %Include qgsstringstatisticalsummary.sip %Include qgsstringutils.sip diff --git a/python/core/qgssqlstatement.sip b/python/core/qgssqlstatement.sip new file mode 100644 index 00000000000..94268053244 --- /dev/null +++ b/python/core/qgssqlstatement.sip @@ -0,0 +1,602 @@ +class QgsSQLStatement +{ +%TypeHeaderCode +#include "qgssqlstatement.h" +%End + + public: + /** + * Creates a new SQL statement based on the provided string. + */ + QgsSQLStatement( const QString& statement ); + ~QgsSQLStatement(); + + //! Returns true if an error occurred when parsing the input statement + bool hasParserError() const; + //! Returns parser error + QString parserErrorString() const; + + /** Performs basic validity checks. Basically checking that columns referencing + * a table, references a specified table. Returns true if the validation is + * succesful */ + bool doBasicValidationChecks( QString& errorMsgOut /Out/ ) const; + + //! Returns root node of the statement. Root node is null is parsing has failed + const QgsSQLStatement::Node* rootNode() const; + + //! Return the original, unmodified statement string. + //! If there was none supplied because it was constructed by sole + //! API calls, dump() will be used to create one instead. + QString statement() const; + + //! Return a statement string, constructed from the internal + //! abstract syntax tree. This does not contain any nice whitespace + //! formatting or comments. In general it is preferrable to use + //! statement() instead. + QString dump() const; + + /** + * @brief list of unary operators + * @note if any change is made here, the definition of QgsSQLStatement::UnaryOperatorText[] must be adapted. + */ + enum UnaryOperator + { + uoNot, + uoMinus, + }; + + /** + * @brief list of binary operators + * @note if any change is made here, the definition of QgsSQLStatement::BinaryOperatorText[] must be adapted. + */ + enum BinaryOperator + { + // logical + boOr, + boAnd, + + // comparison + boEQ, // = + boNE, // <> + boLE, // <= + boGE, // >= + boLT, // < + boGT, // > + boLike, + boNotLike, + boILike, + boNotILike, + boIs, + boIsNot, + + // math + boPlus, + boMinus, + boMul, + boDiv, + boIntDiv, + boMod, + boPow, + + // strings + boConcat, + }; + + /** + * @brief list of join types + * @note if any change is made here, the definition of QgsSQLStatement::JoinTypeText[] must be adapted. + */ + enum JoinType + { + jtDefault, + jtLeft, + jtLeftOuter, + jtRight, + jtRightOuter, + jtCross, + jtInner, + jtFull + }; + + //! @note not available in Python bindings + // static const char* BinaryOperatorText[]; + + //! @note not available in Python bindings + // static const char* UnaryOperatorText[]; + + /** Returns a quoted column reference (in double quotes) + * @see quotedString(), quotedIdentifierIfNeeded() + */ + static QString quotedIdentifier( QString name ); + + /** Returns a quoted column reference (in double quotes) if needed, or + * otherwise the original string. + * @see quotedString(), quotedIdentifier() + */ + static QString quotedIdentifierIfNeeded( QString name ); + + /** Remove double quotes from an identifier. + * @see quotedIdentifier() + */ + static QString stripQuotedIdentifier(QString text); + + /** Returns a quoted version of a string (in single quotes) + * @see quotedIdentifier(), quotedIdentifierIfNeeded() + */ + static QString quotedString( QString text ); + + + ////// + + /** Node type */ + enum NodeType + { + ntUnaryOperator, + ntBinaryOperator, + ntInOperator, + ntBetweenOperator, + ntFunction, + ntLiteral, + ntColumnRef, + ntSelectedColumn, + ntSelect, + ntTableDef, + ntJoin, + ntColumnSorted, + ntCast + }; + + /** Abstract node class */ + class Node + { + %ConvertToSubClassCode + switch (sipCpp->nodeType()) + { + case QgsSQLStatement::ntUnaryOperator: sipType = sipType_QgsSQLStatement_NodeUnaryOperator; break; + case QgsSQLStatement::ntBinaryOperator: sipType = sipType_QgsSQLStatement_NodeBinaryOperator; break; + case QgsSQLStatement::ntInOperator: sipType = sipType_QgsSQLStatement_NodeInOperator; break; + case QgsSQLStatement::ntBetweenOperator: sipType = sipType_QgsSQLStatement_NodeBetweenOperator; break; + case QgsSQLStatement::ntFunction: sipType = sipType_QgsSQLStatement_NodeFunction; break; + case QgsSQLStatement::ntLiteral: sipType = sipType_QgsSQLStatement_NodeLiteral; break; + case QgsSQLStatement::ntColumnRef: sipType = sipType_QgsSQLStatement_NodeColumnRef; break; + case QgsSQLStatement::ntSelectedColumn: sipType = sipType_QgsSQLStatement_NodeSelectedColumn; break; + case QgsSQLStatement::ntSelect: sipType = sipType_QgsSQLStatement_NodeSelect; break; + case QgsSQLStatement::ntTableDef: sipType = sipType_QgsSQLStatement_NodeTableDef; break; + case QgsSQLStatement::ntJoin: sipType = sipType_QgsSQLStatement_NodeJoin; break; + case QgsSQLStatement::ntColumnSorted: sipType = sipType_QgsSQLStatement_NodeColumnSorted; break; + case QgsSQLStatement::ntCast: sipType = sipType_QgsSQLStatement_NodeCast; break; + default: sipType = 0; break; + } + %End + + public: + virtual ~Node(); + + /** + * Abstract virtual that returns the type of this node. + * + * @return The type of this node + */ + virtual QgsSQLStatement::NodeType nodeType() const = 0; + + /** + * Abstract virtual dump method + * + * @return A statement which represents this node as string + */ + virtual QString dump() const = 0; + + /** + * Generate a clone of this node. + * Make sure that the clone does not contain any information which is + * generated in prepare and context related. + * Ownership is transferred to the caller. + * + * @return a deep copy of this node. + */ + virtual QgsSQLStatement::Node* clone() const = 0 /Factory/; + + /** + * Support the visitor pattern. + * + * For any implementation this should look like + * + * C++: + * + * v.visit( *this ); + * + * Python: + * + * v.visit( self) + * + * @param v A visitor that visits this node. + */ + virtual void accept( QgsSQLStatement::Visitor& v ) const = 0; + }; + + /** List of nodes */ + class NodeList + { + public: + NodeList(); + ~NodeList(); + /** Takes ownership of the provided node */ + void append( QgsSQLStatement::Node* node /Transfer/ ); + + /** Returns the number of nodes in the list. + */ + int count() const; + + /** Accept visitor */ + void accept( QgsSQLStatement::Visitor& v ) const; + + const QList& list(); + + /** Creates a deep copy of this list. Ownership is transferred to the caller */ + QgsSQLStatement::NodeList* clone() const /Factory/; + + virtual QString dump() const; + }; + + /** Unary logicial/arithmetical operator ( NOT, - ) */ + class NodeUnaryOperator : QgsSQLStatement::Node + { + public: + NodeUnaryOperator( QgsSQLStatement::UnaryOperator op, QgsSQLStatement::Node* operand /Transfer/ ); + ~NodeUnaryOperator(); + + /** Operator */ + QgsSQLStatement::UnaryOperator op() const; + + /** Operand */ + QgsSQLStatement::Node* operand() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + /** Binary logical/arithmetical operator (AND, OR, =, +, ...) */ + class NodeBinaryOperator : QgsSQLStatement::Node + { + public: + NodeBinaryOperator( QgsSQLStatement::BinaryOperator op, QgsSQLStatement::Node* opLeft /Transfer/, QgsSQLStatement::Node* opRight /Transfer/ ); + ~NodeBinaryOperator(); + + /** Operator */ + QgsSQLStatement::BinaryOperator op() const; + + /** Left operand */ + QgsSQLStatement::Node* opLeft() const; + + /** Right operand */ + QgsSQLStatement::Node* opRight() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + + /** Precedence */ + int precedence() const; + + /** Is left associative ? */ + bool leftAssociative() const; + }; + + /** 'x IN (y, z)' operator */ + class NodeInOperator : QgsSQLStatement::Node + { + public: + NodeInOperator( QgsSQLStatement::Node* node /Transfer/, QgsSQLStatement::NodeList* list /Transfer/, bool notin = false ); + ~NodeInOperator(); + + /** Variable at the left of IN */ + QgsSQLStatement::Node* node() const; + + /** Whether this is a NOT IN operator */ + bool isNotIn() const; + + /** Values list */ + QgsSQLStatement::NodeList* list() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + /** 'X BETWEEN y and z' operator */ + class NodeBetweenOperator : QgsSQLStatement::Node + { + public: + NodeBetweenOperator( QgsSQLStatement::Node* node /Transfer/, QgsSQLStatement::Node* minVal /Transfer/, QgsSQLStatement::Node* maxVal /Transfer/, bool notbetween = false ); + ~NodeBetweenOperator(); + + /** Variable at the left of BETWEEN */ + QgsSQLStatement::Node* node() const; + + /** Whether this is a NOT BETWEEN operator */ + bool isNotBetween() const; + + /** Minimum bound */ + QgsSQLStatement::Node* minVal() const; + + /** Maximum bound */ + QgsSQLStatement::Node* maxVal() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + /** Function with a name and arguments node */ + class NodeFunction : QgsSQLStatement::Node + { + public: + NodeFunction( QString name, QgsSQLStatement::NodeList* args /Transfer/ ); + ~NodeFunction(); + + /** Return function name */ + QString name() const; + + /** Return arguments */ + QgsSQLStatement::NodeList* args() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + /** Literal value (integer, integer64, double, string) */ + class NodeLiteral : QgsSQLStatement::Node + { + public: + NodeLiteral( const QVariant& value ); + + /** The value of the literal. */ + const QVariant& value() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + }; + + /** Reference to a column */ + class NodeColumnRef : QgsSQLStatement::Node + { + public: + NodeColumnRef( const QString& tableName, const QString& name, bool star ); + NodeColumnRef( const QString& name, bool star ); + + /** Set whether this is prefixed by DISTINCT */ + void setDistinct( bool distinct = true ); + + /** The name of the table. May be empty. */ + QString tableName() const; + + /** The name of the column. */ + QString name() const; + + /** Whether this is prefixed by DISTINCT */ + bool distinct() const; + + /** Whether this is the * column */ + bool star() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + /** Clone with same type return */ + QgsSQLStatement::NodeColumnRef* cloneThis() const /Factory/; + }; + + /** Selected column */ + class NodeSelectedColumn : QgsSQLStatement::Node + { + public: + NodeSelectedColumn( QgsSQLStatement::Node* node /Transfer/ ); + + /** Set alias name */ + void setAlias( const QString& alias ); + + /** Column that is refered to */ + QgsSQLStatement::Node* column() const; + + /** Alias name */ + QString alias() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + + /** Clone with same type return */ + QgsSQLStatement::NodeSelectedColumn* cloneThis() const /Factory/; + }; + + /** CAST operator */ + class NodeCast : QgsSQLStatement::Node + { + public: + NodeCast( QgsSQLStatement::Node* node /Transfer/, const QString& type ); + + /** Node that is refered to */ + QgsSQLStatement::Node* node() const; + + /** Type */ + QString type() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + /** Table definition */ + class NodeTableDef : QgsSQLStatement::Node + { + public: + NodeTableDef( const QString& name, const QString& alias ); + NodeTableDef( const QString& name ); + + /** Table name */ + QString name() const; + + /** Table alias */ + QString alias() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + /** Clone with same type return */ + QgsSQLStatement::NodeTableDef* cloneThis() const /Factory/; + }; + + /** Join definition */ + class NodeJoin : QgsSQLStatement::Node + { + public: + NodeJoin( QgsSQLStatement::NodeTableDef* tabledef /Transfer/, QgsSQLStatement::Node* onExpr /Transfer/, QgsSQLStatement::JoinType type ); + NodeJoin( QgsSQLStatement::NodeTableDef* tabledef /Transfer/, QList usingColumns, QgsSQLStatement::JoinType type ); + + /** Table definition */ + QgsSQLStatement::NodeTableDef* tableDef() const; + + /** On expression. Will be nullptr if usingColumns() is not empty */ + QgsSQLStatement::Node* onExpr() const; + + /** Columns referenced by USING */ + QList usingColumns() const; + + /** Join type */ + QgsSQLStatement::JoinType type() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + /** Clone with same type return */ + QgsSQLStatement::NodeJoin* cloneThis() const /Factory/; + }; + + /** Column in a ORDER BY */ + class NodeColumnSorted : QgsSQLStatement::Node + { + public: + NodeColumnSorted( QgsSQLStatement::NodeColumnRef* column /Transfer/, bool asc ); + + /** The name of the column. */ + QgsSQLStatement::NodeColumnRef* column() const; + + /** Whether the column is sorted in ascending order */ + bool ascending() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + /** Clone with same type return */ + QgsSQLStatement::NodeColumnSorted* cloneThis() const /Factory/; + }; + + /** SELECT node */ + class NodeSelect : QgsSQLStatement::Node + { + public: + NodeSelect( QList tableList /Transfer/, QList columns /Transfer/, bool distinct ); + + /** Set joins */ + void setJoins( QList joins /Transfer/ ); + /** Append a join */ + void appendJoin( QgsSQLStatement::NodeJoin* join /Transfer/ ); + /** Set where clause */ + void setWhere( QgsSQLStatement::Node* where /Transfer/ ); + /** Set order by columns */ + void setOrderBy( QList orderBy /Transfer/ ); + + /** Return the list of tables */ + QList tables() const; + /** Return the list of columns */ + QList columns() const; + /** Return if the SELECT is DISTINCT */ + bool distinct() const; + /** Return the list of joins */ + QList joins() const; + /** Return the where clause */ + QgsSQLStatement::Node* where() const; + /** Return the list of order by columns */ + QList orderBy() const; + + virtual QgsSQLStatement::NodeType nodeType() const; + virtual QString dump() const; + + virtual void accept( QgsSQLStatement::Visitor& v ) const; + virtual QgsSQLStatement::Node* clone() const /Factory/; + }; + + ////// + + /** Support for visitor pattern - algorithms dealing with the statement + may be implemented without modifying the Node classes */ + class Visitor + { + public: + virtual ~Visitor(); + virtual void visit( const QgsSQLStatement::NodeUnaryOperator& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeBinaryOperator& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeInOperator& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeBetweenOperator& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeFunction& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeLiteral& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeColumnRef& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeSelectedColumn& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeTableDef& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeJoin& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeColumnSorted& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeSelect& n ) = 0; + virtual void visit( const QgsSQLStatement::NodeCast& n ) = 0; + }; + + /** A visitor that recursively explores all children */ + class RecursiveVisitor: public QgsSQLStatement::Visitor + { + public: + RecursiveVisitor(); + + virtual void visit( const QgsSQLStatement::NodeUnaryOperator& n ); + virtual void visit( const QgsSQLStatement::NodeBinaryOperator& n ); + virtual void visit( const QgsSQLStatement::NodeInOperator& n ); + virtual void visit( const QgsSQLStatement::NodeBetweenOperator& n) ; + virtual void visit( const QgsSQLStatement::NodeFunction& n ); + virtual void visit( const QgsSQLStatement::NodeLiteral& n ); + virtual void visit( const QgsSQLStatement::NodeColumnRef& n ); + virtual void visit( const QgsSQLStatement::NodeSelectedColumn& n ); + virtual void visit( const QgsSQLStatement::NodeTableDef& n ); + virtual void visit( const QgsSQLStatement::NodeJoin& n ); + virtual void visit( const QgsSQLStatement::NodeColumnSorted& n ); + virtual void visit( const QgsSQLStatement::NodeSelect& n ); + virtual void visit( const QgsSQLStatement::NodeCast& n ); + }; + + /** Entry function for the visitor pattern */ + void acceptVisitor( QgsSQLStatement::Visitor& v ) const; +}; diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 5e7790e2bb4..b31dceb73fa 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -194,6 +194,7 @@ SET(QGIS_CORE_SRCS qgsspatialindex.cpp qgssqlexpressioncompiler.cpp qgssqliteexpressioncompiler.cpp + qgssqlstatement.cpp qgsstatisticalsummary.cpp qgsstringstatisticalsummary.cpp qgsstringutils.cpp @@ -427,11 +428,11 @@ IF (QT_MOBILITY_LOCATION_FOUND OR Qt5Positioning_FOUND) ) ENDIF (QT_MOBILITY_LOCATION_FOUND OR Qt5Positioning_FOUND) -ADD_FLEX_FILES(QGIS_CORE_SRCS qgsexpressionlexer.ll) -ADD_BISON_FILES(QGIS_CORE_SRCS qgsexpressionparser.yy) +ADD_FLEX_FILES(QGIS_CORE_SRCS qgsexpressionlexer.ll qgssqlstatementlexer.ll) +ADD_BISON_FILES(QGIS_CORE_SRCS qgsexpressionparser.yy qgssqlstatementparser.yy) IF(NOT MSVC) - SET_SOURCE_FILES_PROPERTIES(qgsexpressionparser.cpp PROPERTIES COMPILE_FLAGS -w) + SET_SOURCE_FILES_PROPERTIES(qgsexpressionparser.cpp qgssqlstatementparser.cpp PROPERTIES COMPILE_FLAGS -w) ELSE(NOT MSVC) # -wd4702 unreachable code SET_SOURCE_FILES_PROPERTIES( diff --git a/src/core/qgssqlstatement.cpp b/src/core/qgssqlstatement.cpp new file mode 100644 index 00000000000..92bee0bf7e2 --- /dev/null +++ b/src/core/qgssqlstatement.cpp @@ -0,0 +1,691 @@ +/*************************************************************************** + qgssqlstatement.cpp + ------------------- + begin : April 2016 + copyright : (C) 2011 by Martin Dobias + copyright : (C) 2016 by Even Rouault + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgssqlstatement.h" + +#include +#include + +static const QRegExp identifierRE( "^[A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*$" ); + +// from parser +extern QgsSQLStatement::Node* parse( const QString& str, QString& parserErrorMsg ); + +/////////////////////////////////////////////// +// operators + +const char* QgsSQLStatement::BinaryOperatorText[] = +{ + // this must correspond (number and order of element) to the declaration of the enum BinaryOperator + "OR", "AND", + "=", "<>", "<=", ">=", "<", ">", "LIKE", "NOT LIKE", "ILIKE", "NOT ILIKE", "IS", "IS NOT", + "+", "-", "*", "/", "//", "%", "^", + "||" +}; + +const char* QgsSQLStatement::UnaryOperatorText[] = +{ + // this must correspond (number and order of element) to the declaration of the enum UnaryOperator + "NOT", "-" +}; + +const char* QgsSQLStatement::JoinTypeText[] = +{ + // this must correspond (number and order of element) to the declaration of the enum JoinType + "", "LEFT", "LEFT OUTER", "RIGHT", "RIGHT OUTER", "CROSS", "INNER", "FULL" +}; + +////// + +QString QgsSQLStatement::statement() const +{ + if ( !mStatement.isNull() ) + return mStatement; + else + return dump(); +} + +QString QgsSQLStatement::dump() const +{ + if ( !mRootNode ) + return tr( "(no root)" ); + + return mRootNode->dump(); +} + +QString QgsSQLStatement::quotedIdentifier( QString name ) +{ + return QString( "\"%1\"" ).arg( name.replace( '\"', "\"\"" ) ); +} + +QString QgsSQLStatement::quotedIdentifierIfNeeded( QString name ) +{ + return identifierRE.exactMatch( name ) ? name : quotedIdentifier( name ); +} + +QString QgsSQLStatement::stripQuotedIdentifier( QString text ) +{ + if ( text.length() >= 2 && text[0] == '"' && text[text.length()-1] == '"' ) + { + // strip double quotes on start,end + text = text.mid( 1, text.length() - 2 ); + + // make single "double quotes" from double "double quotes" + text.replace( "\"\"", "\"" ); + } + return text; +} + +QString QgsSQLStatement::quotedString( QString text ) +{ + text.replace( '\'', "''" ); + text.replace( '\\', "\\\\" ); + text.replace( '\n', "\\n" ); + text.replace( '\t', "\\t" ); + return QString( "'%1'" ).arg( text ); +} + +QgsSQLStatement::QgsSQLStatement( const QString& expr ) +{ + mRootNode = ::parse( expr, mParserErrorString ); + mStatement = expr; +} + +QgsSQLStatement::QgsSQLStatement( const QgsSQLStatement& other ) +{ + mRootNode = ::parse( other.mStatement, mParserErrorString ); + mStatement = other.mStatement; +} + +QgsSQLStatement& QgsSQLStatement::operator=( const QgsSQLStatement & other ) +{ + if ( &other != this ) + { + delete mRootNode; + mParserErrorString.clear(); + mRootNode = ::parse( other.mStatement, mParserErrorString ); + mStatement = other.mStatement; + } + return *this; +} + +QgsSQLStatement::~QgsSQLStatement() +{ + delete mRootNode; +} + +bool QgsSQLStatement::hasParserError() const { return !mParserErrorString.isNull(); } + +QString QgsSQLStatement::parserErrorString() const { return mParserErrorString; } + +void QgsSQLStatement::acceptVisitor( QgsSQLStatement::Visitor& v ) const +{ + if ( mRootNode ) + mRootNode->accept( v ); +} + +const QgsSQLStatement::Node* QgsSQLStatement::rootNode() const +{ + return mRootNode; +} + +void QgsSQLStatement::RecursiveVisitor::visit( const QgsSQLStatement::NodeSelect& n ) +{ + Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, n.tables() ) + { + table->accept( *this ); + } + Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, n.columns() ) + { + column->accept( *this ); + } + Q_FOREACH ( QgsSQLStatement::NodeJoin* join, n.joins() ) + { + join->accept( *this ); + } + QgsSQLStatement::Node* where = n.where(); + if ( where ) + where->accept( *this ); + Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* column, n.orderBy() ) + { + column->accept( *this ); + } +} + +void QgsSQLStatement::RecursiveVisitor::visit( const QgsSQLStatement::NodeJoin& n ) +{ + n.tableDef()->accept( *this ); + QgsSQLStatement::Node* expr = n.onExpr(); + if ( expr ) + expr->accept( *this ); +} + +/** Internal use. + * @note not available in Python bindings + */ +class QgsSQLStatementCollectTableNames: public QgsSQLStatement::RecursiveVisitor +{ + public: + typedef QPair TableColumnPair; + + QgsSQLStatementCollectTableNames() {} + + void visit( const QgsSQLStatement::NodeColumnRef& n ) override; + void visit( const QgsSQLStatement::NodeTableDef& n ) override; + + QSet tableNamesDeclared; + QSet tableNamesReferenced; +}; + +void QgsSQLStatementCollectTableNames::visit( const QgsSQLStatement::NodeColumnRef& n ) +{ + if ( !n.tableName().isEmpty() ) + tableNamesReferenced.insert( TableColumnPair( n.tableName(), n.name() ) ); + QgsSQLStatement::RecursiveVisitor::visit( n ); +} + +void QgsSQLStatementCollectTableNames::visit( const QgsSQLStatement::NodeTableDef& n ) +{ + tableNamesDeclared.insert( n.alias().isEmpty() ? n.name() : n.alias() ); + QgsSQLStatement::RecursiveVisitor::visit( n ); +} + +bool QgsSQLStatement::doBasicValidationChecks( QString& errorMsgOut ) const +{ + errorMsgOut.clear(); + if ( mRootNode == nullptr ) + { + errorMsgOut = tr( "No root node" ); + return false; + } + QgsSQLStatementCollectTableNames v; + mRootNode->accept( v ); + + Q_FOREACH ( QgsSQLStatementCollectTableNames::TableColumnPair pair, v.tableNamesReferenced ) + { + if ( !v.tableNamesDeclared.contains( pair.first ) ) + { + if ( !errorMsgOut.isEmpty() ) + errorMsgOut += " "; + errorMsgOut += QString( tr( "Table %1 is referenced by column %2, but not selected in FROM / JOIN." ) ).arg( pair.first ).arg( pair.second ); + } + } + + return errorMsgOut.isEmpty(); +} + +/////////////////////////////////////////////// +// nodes + +QgsSQLStatement::NodeList* QgsSQLStatement::NodeList::clone() const +{ + NodeList* nl = new NodeList; + Q_FOREACH ( Node* node, mList ) + { + nl->mList.append( node->clone() ); + } + + return nl; +} + +QString QgsSQLStatement::NodeList::dump() const +{ + QString msg; + bool first = true; + Q_FOREACH ( Node* n, mList ) + { + if ( !first ) msg += ", "; + else first = false; + msg += n->dump(); + } + return msg; +} + + +// + +QString QgsSQLStatement::NodeUnaryOperator::dump() const +{ + return QString( "%1 %2" ).arg( UnaryOperatorText[mOp], mOperand->dump() ); +} + +QgsSQLStatement::Node*QgsSQLStatement::NodeUnaryOperator::clone() const +{ + return new NodeUnaryOperator( mOp, mOperand->clone() ); +} + +// + +int QgsSQLStatement::NodeBinaryOperator::precedence() const +{ + // see left/right in qgsexpressionparser.yy + switch ( mOp ) + { + case boOr: + return 1; + + case boAnd: + return 2; + + case boEQ: + case boNE: + case boLE: + case boGE: + case boLT: + case boGT: + case boLike: + case boILike: + case boNotLike: + case boNotILike: + case boIs: + case boIsNot: + return 3; + + case boPlus: + case boMinus: + return 4; + + case boMul: + case boDiv: + case boIntDiv: + case boMod: + return 5; + + case boPow: + return 6; + + case boConcat: + return 7; + } + Q_ASSERT( 0 && "unexpected binary operator" ); + return -1; +} + +bool QgsSQLStatement::NodeBinaryOperator::leftAssociative() const +{ + // see left/right in qgsexpressionparser.yy + switch ( mOp ) + { + case boOr: + case boAnd: + case boEQ: + case boNE: + case boLE: + case boGE: + case boLT: + case boGT: + case boLike: + case boILike: + case boNotLike: + case boNotILike: + case boIs: + case boIsNot: + case boPlus: + case boMinus: + case boMul: + case boDiv: + case boIntDiv: + case boMod: + case boConcat: + return true; + + case boPow: + return false; + } + Q_ASSERT( 0 && "unexpected binary operator" ); + return false; +} + +QString QgsSQLStatement::NodeBinaryOperator::dump() const +{ + QgsSQLStatement::NodeBinaryOperator *lOp = dynamic_cast( mOpLeft ); + QgsSQLStatement::NodeBinaryOperator *rOp = dynamic_cast( mOpRight ); + QgsSQLStatement::NodeUnaryOperator *ruOp = dynamic_cast( mOpRight ); + + QString rdump( mOpRight->dump() ); + + // avoid dumping "IS (NOT ...)" as "IS NOT ..." + if ( mOp == boIs && ruOp && ruOp->op() == uoNot ) + { + rdump.prepend( '(' ).append( ')' ); + } + + QString fmt; + if ( leftAssociative() ) + { + fmt += lOp && ( lOp->precedence() < precedence() ) ? "(%1)" : "%1"; + fmt += " %2 "; + fmt += rOp && ( rOp->precedence() <= precedence() ) ? "(%3)" : "%3"; + } + else + { + fmt += lOp && ( lOp->precedence() <= precedence() ) ? "(%1)" : "%1"; + fmt += " %2 "; + fmt += rOp && ( rOp->precedence() < precedence() ) ? "(%3)" : "%3"; + } + + return fmt.arg( mOpLeft->dump(), BinaryOperatorText[mOp], rdump ); +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeBinaryOperator::clone() const +{ + return new NodeBinaryOperator( mOp, mOpLeft->clone(), mOpRight->clone() ); +} + +// + +QString QgsSQLStatement::NodeInOperator::dump() const +{ + return QString( "%1 %2IN (%3)" ).arg( mNode->dump(), mNotIn ? "NOT " : "", mList->dump() ); +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeInOperator::clone() const +{ + return new NodeInOperator( mNode->clone(), mList->clone(), mNotIn ); +} + +// + +QString QgsSQLStatement::NodeBetweenOperator::dump() const +{ + return QString( "%1 %2BETWEEN %3 AND %4" ).arg( mNode->dump(), mNotBetween ? "NOT " : "", mMinVal->dump(), mMaxVal->dump() ); +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeBetweenOperator::clone() const +{ + return new NodeBetweenOperator( mNode->clone(), mMinVal->clone(), mMaxVal->clone(), mNotBetween ); +} + +// + +QString QgsSQLStatement::NodeFunction::dump() const +{ + return QString( "%1(%2)" ).arg( mName, mArgs ? mArgs->dump() : QString() ); // function +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeFunction::clone() const +{ + return new NodeFunction( mName, mArgs ? mArgs->clone() : nullptr ); +} + +// + +QString QgsSQLStatement::NodeLiteral::dump() const +{ + if ( mValue.isNull() ) + return "NULL"; + + switch ( mValue.type() ) + { + case QVariant::Int: + return QString::number( mValue.toInt() ); + case QVariant::LongLong: + return QString::number( mValue.toLongLong() ); + case QVariant::Double: + return QString::number( mValue.toDouble() ); + case QVariant::String: + return quotedString( mValue.toString() ); + case QVariant::Bool: + return mValue.toBool() ? "TRUE" : "FALSE"; + default: + return tr( "[unsupported type;%1; value:%2]" ).arg( mValue.typeName(), mValue.toString() ); + } +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeLiteral::clone() const +{ + return new NodeLiteral( mValue ); +} + +// + +QString QgsSQLStatement::NodeColumnRef::dump() const +{ + QString ret; + if ( mDistinct ) + ret += "DISTINCT "; + if ( !mTableName.isEmpty() ) + { + ret += quotedIdentifierIfNeeded( mTableName ); + ret += '.'; + } + ret += ( mStar ) ? mName : quotedIdentifierIfNeeded( mName ); + return ret; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeColumnRef::clone() const +{ + return cloneThis(); +} + +QgsSQLStatement::NodeColumnRef* QgsSQLStatement::NodeColumnRef::cloneThis() const +{ + NodeColumnRef* newColumnRef = new NodeColumnRef( mTableName, mName, mStar ); + newColumnRef->setDistinct( mDistinct ); + return newColumnRef; +} + +// + +QString QgsSQLStatement::NodeSelectedColumn::dump() const +{ + QString ret; + ret += mColumnNode->dump(); + if ( !mAlias.isEmpty() ) + { + ret += " AS "; + ret += quotedIdentifierIfNeeded( mAlias ); + } + return ret; +} + +QgsSQLStatement::NodeSelectedColumn* QgsSQLStatement::NodeSelectedColumn::cloneThis() const +{ + NodeSelectedColumn* newObj = new NodeSelectedColumn( mColumnNode->clone() ); + newObj->setAlias( mAlias ); + return newObj; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeSelectedColumn::clone() const +{ + return cloneThis(); +} +// + +QString QgsSQLStatement::NodeTableDef::dump() const +{ + QString ret; + ret = quotedIdentifierIfNeeded( mName ); + if ( !mAlias.isEmpty() ) + { + ret += " AS "; + ret += quotedIdentifierIfNeeded( mAlias ); + } + return ret; +} + +QgsSQLStatement::NodeTableDef* QgsSQLStatement::NodeTableDef::cloneThis() const +{ + return new NodeTableDef( mName, mAlias ); +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeTableDef::clone() const +{ + return cloneThis(); +} + +// + +QString QgsSQLStatement::NodeSelect::dump() const +{ + QString ret = "SELECT "; + if ( mDistinct ) + ret += "DISTINCT "; + bool bFirstColumn = true; + Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, mColumns ) + { + if ( !bFirstColumn ) + ret += ", "; + bFirstColumn = false; + ret += column->dump(); + } + ret += " FROM "; + bool bFirstTable = true; + Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, mTableList ) + { + if ( !bFirstTable ) + ret += ", "; + bFirstTable = false; + ret += table->dump(); + } + Q_FOREACH ( QgsSQLStatement::NodeJoin* join, mJoins ) + { + ret += ' '; + ret += join->dump(); + } + if ( mWhere != nullptr ) + { + ret += " WHERE "; + ret += mWhere->dump(); + } + if ( !mOrderBy.isEmpty() ) + { + ret += " ORDER BY "; + bool bFirst = true; + Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* orderBy, mOrderBy ) + { + if ( !bFirst ) + ret += ", "; + bFirst = false; + ret += orderBy->dump(); + } + } + return ret; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeSelect::clone() const +{ + QList newColumnList; + Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, mColumns ) + { + newColumnList.push_back( column->cloneThis() ); + } + QList newTableList; + Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, mTableList ) + { + newTableList.push_back( table->cloneThis() ); + } + QgsSQLStatement::NodeSelect* newSelect = new NodeSelect( newTableList, newColumnList, mDistinct ); + Q_FOREACH ( QgsSQLStatement::NodeJoin* join, mJoins ) + { + newSelect->appendJoin( join->cloneThis() ); + } + if ( mWhere != nullptr ) + { + newSelect->setWhere( mWhere->clone() ); + } + QList newOrderByList; + Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* columnSorted, mOrderBy ) + { + newOrderByList.push_back( columnSorted->cloneThis() ); + } + newSelect->setOrderBy( newOrderByList ); + return newSelect; +} + +// + +QString QgsSQLStatement::NodeJoin::dump() const +{ + QString ret; + if ( mType != jtDefault ) + { + ret += JoinTypeText[mType]; + ret += " "; + } + ret += "JOIN "; + ret += mTableDef->dump(); + if ( mOnExpr != nullptr ) + { + ret += " ON "; + ret += mOnExpr->dump(); + } + else + { + ret += " USING ("; + bool first = true; + Q_FOREACH ( QString column, mUsingColumns ) + { + if ( !first ) + ret += ", "; + first = false; + ret += quotedIdentifierIfNeeded( column ); + } + ret += ")"; + } + return ret; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeJoin::clone() const +{ + return cloneThis(); +} + +QgsSQLStatement::NodeJoin* QgsSQLStatement::NodeJoin::cloneThis() const +{ + if ( mOnExpr != nullptr ) + return new NodeJoin( mTableDef->cloneThis(), mOnExpr->clone(), mType ); + else + return new NodeJoin( mTableDef->cloneThis(), mUsingColumns, mType ); +} + +// + +QString QgsSQLStatement::NodeColumnSorted::dump() const +{ + QString ret; + ret = mColumn->dump(); + if ( !mAsc ) + ret += " DESC"; + return ret; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeColumnSorted::clone() const +{ + return cloneThis(); +} + +QgsSQLStatement::NodeColumnSorted* QgsSQLStatement::NodeColumnSorted::cloneThis() const +{ + return new NodeColumnSorted( mColumn->cloneThis(), mAsc ); +} + +// + +QString QgsSQLStatement::NodeCast::dump() const +{ + QString ret( "CAST(" ); + ret += mNode->dump(); + ret += " AS "; + ret += mType; + ret += ')'; + return ret; +} + +QgsSQLStatement::Node* QgsSQLStatement::NodeCast::clone() const +{ + return new NodeCast( mNode->clone(), mType ); +} diff --git a/src/core/qgssqlstatement.h b/src/core/qgssqlstatement.h new file mode 100644 index 00000000000..fe9f5af467c --- /dev/null +++ b/src/core/qgssqlstatement.h @@ -0,0 +1,740 @@ +/*************************************************************************** + qgssqlstatement.h + --------------------- + begin : April 2016 + copyright : (C) 2011 by Martin Dobias + copyright : (C) 2016 by Even Rouault + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSSQLSTATEMENT_H +#define QGSSQLSTATEMENT_H + +#include +#include +#include +#include +#include +#include + +/** +Class for parsing SQL statements. +* @note Added in QGIS 2.16 +*/ + +class CORE_EXPORT QgsSQLStatement +{ + Q_DECLARE_TR_FUNCTIONS( QgsSQLStatement ) + public: + /** + * Creates a new statement based on the provided string. + */ + QgsSQLStatement( const QString& statement ); + + /** + * Create a copy of this statement. + */ + QgsSQLStatement( const QgsSQLStatement& other ); + /** + * Create a copy of this statement. + */ + QgsSQLStatement& operator=( const QgsSQLStatement& other ); + ~QgsSQLStatement(); + + //! Returns true if an error occurred when parsing the input statement + bool hasParserError() const; + //! Returns parser error + QString parserErrorString() const; + + /** Performs basic validity checks. Basically checking that columns referencing + * a table, references a specified table. Returns true if the validation is + * succesful */ + bool doBasicValidationChecks( QString& errorMsgOut ) const; + + class Node; + + //! Returns root node of the statement. Root node is null is parsing has failed + const Node* rootNode() const; + + //! Return the original, unmodified statement string. + //! If there was none supplied because it was constructed by sole + //! API calls, dump() will be used to create one instead. + QString statement() const; + + //! Return statement string, constructed from the internal + //! abstract syntax tree. This does not contain any nice whitespace + //! formatting or comments. In general it is preferrable to use + //! statement() instead. + QString dump() const; + + /** Returns a quoted column reference (in double quotes) + * @see quotedString(), quotedIdentifierIfNeeded() + */ + static QString quotedIdentifier( QString name ); + + /** Returns a quoted column reference (in double quotes) if needed, or + * otherwise the original string. + * @see quotedString(), quotedIdentifier() + */ + static QString quotedIdentifierIfNeeded( QString name ); + + /** Remove double quotes from an identifier. + * @see quotedIdentifier() + */ + static QString stripQuotedIdentifier( QString text ); + + /** Returns a quoted version of a string (in single quotes) + * @see quotedIdentifier(), quotedIdentifierIfNeeded() + */ + static QString quotedString( QString text ); + + /** + * @brief list of unary operators + * @note if any change is made here, the definition of QgsSQLStatement::UnaryOperatorText[] must be adapted. + */ + enum UnaryOperator + { + uoNot, + uoMinus, + }; + + /** + * @brief list of binary operators + * @note if any change is made here, the definition of QgsSQLStatement::BinaryOperatorText[] must be adapted. + */ + enum BinaryOperator + { + // logical + boOr, + boAnd, + + // comparison + boEQ, // = + boNE, // <> + boLE, // <= + boGE, // >= + boLT, // < + boGT, // > + boLike, + boNotLike, + boILike, + boNotILike, + boIs, + boIsNot, + + // math + boPlus, + boMinus, + boMul, + boDiv, + boIntDiv, + boMod, + boPow, + + // strings + boConcat, + }; + + /** + * @brief list of join types + * @note if any change is made here, the definition of QgsSQLStatement::JoinTypeText[] must be adapted. + */ + enum JoinType + { + jtDefault, + jtLeft, + jtLeftOuter, + jtRight, + jtRightOuter, + jtCross, + jtInner, + jtFull + }; + + //! @note not available in Python bindings + static const char* BinaryOperatorText[]; + + //! @note not available in Python bindings + static const char* UnaryOperatorText[]; + + //! @note not available in Python bindings + static const char* JoinTypeText[]; + + ////// + + class Visitor; // visitor interface is defined below + + /** Node type */ + enum NodeType + { + ntUnaryOperator, + ntBinaryOperator, + ntInOperator, + ntBetweenOperator, + ntFunction, + ntLiteral, + ntColumnRef, + ntSelectedColumn, + ntSelect, + ntTableDef, + ntJoin, + ntColumnSorted, + ntCast + }; + + /** Abstract node class */ + class CORE_EXPORT Node + { + public: + virtual ~Node() {} + + /** + * Abstract virtual that returns the type of this node. + * + * @return The type of this node + */ + virtual NodeType nodeType() const = 0; + + /** + * Abstract virtual dump method + * + * @return A statement which represents this node as string + */ + virtual QString dump() const = 0; + + /** + * Generate a clone of this node. + * Make sure that the clone does not contain any information which is + * generated in prepare and context related. + * Ownership is transferred to the caller. + * + * @return a deep copy of this node. + */ + virtual Node* clone() const = 0; + + /** + * Support the visitor pattern. + * + * For any implementation this should look like + * + * C++: + * + * v.visit( *this ); + * + * Python: + * + * v.visit( self) + * + * @param v A visitor that visits this node. + */ + virtual void accept( Visitor& v ) const = 0; + }; + + /** List of nodes */ + class CORE_EXPORT NodeList + { + public: + /** Constructor */ + NodeList() {} + virtual ~NodeList() { qDeleteAll( mList ); } + + /** Takes ownership of the provided node */ + void append( Node* node ) { mList.append( node ); } + + /** Return list */ + QList list() { return mList; } + + /** Returns the number of nodes in the list. + */ + int count() const { return mList.count(); } + + /** Accept visitor */ + void accept( Visitor& v ) const { Q_FOREACH ( Node* node, mList ) { node->accept( v ); } } + + /** Creates a deep copy of this list. Ownership is transferred to the caller */ + NodeList* clone() const; + + /** Dump list */ + virtual QString dump() const; + + protected: + QList mList; + }; + + /** Unary logicial/arithmetical operator ( NOT, - ) */ + class CORE_EXPORT NodeUnaryOperator : public Node + { + public: + /** Constructor */ + NodeUnaryOperator( UnaryOperator op, Node* operand ) : mOp( op ), mOperand( operand ) {} + ~NodeUnaryOperator() { delete mOperand; } + + /** Operator */ + UnaryOperator op() const { return mOp; } + + /** Operand */ + Node* operand() const { return mOperand; } + + virtual NodeType nodeType() const override { return ntUnaryOperator; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + UnaryOperator mOp; + Node* mOperand; + }; + + /** Binary logical/arithmetical operator (AND, OR, =, +, ...) */ + class CORE_EXPORT NodeBinaryOperator : public Node + { + public: + /** Constructor */ + NodeBinaryOperator( BinaryOperator op, Node* opLeft, Node* opRight ) : mOp( op ), mOpLeft( opLeft ), mOpRight( opRight ) {} + ~NodeBinaryOperator() { delete mOpLeft; delete mOpRight; } + + /** Operator */ + BinaryOperator op() const { return mOp; } + + /** Left operand */ + Node* opLeft() const { return mOpLeft; } + + /** Right operand */ + Node* opRight() const { return mOpRight; } + + virtual NodeType nodeType() const override { return ntBinaryOperator; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + /** Precedence */ + int precedence() const; + + /** Is left associative ? */ + bool leftAssociative() const; + + protected: + + BinaryOperator mOp; + Node* mOpLeft; + Node* mOpRight; + }; + + /** 'x IN (y, z)' operator */ + class CORE_EXPORT NodeInOperator : public Node + { + public: + /** Constructor */ + NodeInOperator( Node* node, NodeList* list, bool notin = false ) : mNode( node ), mList( list ), mNotIn( notin ) {} + virtual ~NodeInOperator() { delete mNode; delete mList; } + + /** Variable at the left of IN */ + Node* node() const { return mNode; } + + /** Whether this is a NOT IN operator */ + bool isNotIn() const { return mNotIn; } + + /** Values list */ + NodeList* list() const { return mList; } + + virtual NodeType nodeType() const override { return ntInOperator; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + Node* mNode; + NodeList* mList; + bool mNotIn; + }; + + /** 'X BETWEEN y and z' operator */ + class CORE_EXPORT NodeBetweenOperator : public Node + { + public: + /** Constructor */ + NodeBetweenOperator( Node* node, Node* minVal, Node* maxVal, bool notBetween = false ) : mNode( node ), mMinVal( minVal ), mMaxVal( maxVal ), mNotBetween( notBetween ) {} + virtual ~NodeBetweenOperator() { delete mNode; delete mMinVal; delete mMaxVal; } + + /** Variable at the left of BETWEEN */ + Node* node() const { return mNode; } + + /** Whether this is a NOT BETWEEN operator */ + bool isNotBetween() const { return mNotBetween; } + + /** Minimum bound */ + Node* minVal() const { return mMinVal; } + + /** Maximum bound */ + Node* maxVal() const { return mMaxVal; } + + virtual NodeType nodeType() const override { return ntBetweenOperator; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + Node* mNode; + Node* mMinVal; + Node* mMaxVal; + bool mNotBetween; + }; + + /** Function with a name and arguments node */ + class CORE_EXPORT NodeFunction : public Node + { + public: + /** Constructor */ + NodeFunction( QString name, NodeList* args ) : mName( name ), mArgs( args ) {} + virtual ~NodeFunction() { delete mArgs; } + + /** Return function name */ + QString name() const { return mName; } + + /** Return arguments */ + NodeList* args() const { return mArgs; } + + virtual NodeType nodeType() const override { return ntFunction; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + QString mName; + NodeList* mArgs; + + }; + + /** Literal value (integer, integer64, double, string) */ + class CORE_EXPORT NodeLiteral : public Node + { + public: + /** Constructor */ + NodeLiteral( const QVariant& value ) : mValue( value ) {} + + /** The value of the literal. */ + inline QVariant value() const { return mValue; } + + virtual NodeType nodeType() const override { return ntLiteral; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + QVariant mValue; + }; + + /** Reference to a column */ + class CORE_EXPORT NodeColumnRef : public Node + { + public: + /** Constructor with colum name only */ + NodeColumnRef( const QString& name, bool star ) : mName( name ), mDistinct( false ), mStar( star ) {} + /** Constructor with table and column name */ + NodeColumnRef( const QString& tableName, const QString& name, bool star ) : mTableName( tableName ), mName( name ), mDistinct( false ), mStar( star ) {} + + /** Set whether this is prefixed by DISTINCT */ + void setDistinct( bool distinct = true ) { mDistinct = distinct; } + + /** The name of the table. May be empty. */ + QString tableName() const { return mTableName; } + + /** The name of the column. */ + QString name() const { return mName; } + + /** Whether this is the * column */ + bool star() const { return mStar; } + + /** Whether this is prefixed by DISTINCT */ + bool distinct() const { return mDistinct; } + + virtual NodeType nodeType() const override { return ntColumnRef; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + /** Clone with same type return */ + NodeColumnRef* cloneThis() const; + + protected: + QString mTableName; + QString mName; + bool mDistinct; + bool mStar; + }; + + /** Selected column */ + class CORE_EXPORT NodeSelectedColumn : public Node + { + public: + /** Constructor */ + NodeSelectedColumn( Node* node ) : mColumnNode( node ) {} + virtual ~NodeSelectedColumn() { delete mColumnNode; } + + /** Set alias name */ + void setAlias( const QString& alias ) { mAlias = alias; } + + /** Column that is refered to */ + Node* column() const { return mColumnNode; } + + /** Alias name */ + QString alias() const { return mAlias; } + + virtual NodeType nodeType() const override { return ntSelectedColumn; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + /** Clone with same type return */ + NodeSelectedColumn* cloneThis() const; + + protected: + Node *mColumnNode; + QString mAlias; + }; + + /** CAST operator */ + class CORE_EXPORT NodeCast : public Node + { + public: + /** Constructor */ + NodeCast( Node* node, const QString& type ) : mNode( node ), mType( type ) {} + virtual ~NodeCast() { delete mNode; } + + /** Node that is refered to */ + Node* node() const { return mNode; } + + /** Type */ + QString type() const { return mType; } + + virtual NodeType nodeType() const override { return ntCast; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + Node *mNode; + QString mType; + }; + + /** Table definition */ + class CORE_EXPORT NodeTableDef : public Node + { + public: + /** Constructor with table name */ + NodeTableDef( const QString& name ) : mName( name ) {} + /** Constructor with table name and alias */ + NodeTableDef( const QString& name, const QString& alias ) : mName( name ), mAlias( alias ) {} + + /** Table name */ + QString name() const { return mName; } + + /** Table alias */ + QString alias() const { return mAlias; } + + virtual NodeType nodeType() const override { return ntTableDef; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + /** Clone with same type return */ + NodeTableDef* cloneThis() const; + + protected: + QString mName; + QString mAlias; + }; + + /** Join definition */ + class CORE_EXPORT NodeJoin : public Node + { + public: + /** Constructor with table definition, ON expression */ + NodeJoin( NodeTableDef* tabledef, Node* onExpr, JoinType type ) : mTableDef( tabledef ), mOnExpr( onExpr ), mType( type ) {} + /** Constructor with table definition and USING columns */ + NodeJoin( NodeTableDef* tabledef, QList usingColumns, JoinType type ) : mTableDef( tabledef ), mOnExpr( nullptr ), mUsingColumns( usingColumns ), mType( type ) {} + virtual ~NodeJoin() { delete mTableDef; delete mOnExpr; } + + /** Table definition */ + NodeTableDef* tableDef() const { return mTableDef; } + + /** On expression. Will be nullptr if usingColumns() is not empty */ + Node* onExpr() const { return mOnExpr; } + + /** Columns referenced by USING */ + QList usingColumns() const { return mUsingColumns; } + + /** Join type */ + JoinType type() const { return mType; } + + virtual NodeType nodeType() const override { return ntJoin; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + /** Clone with same type return */ + NodeJoin* cloneThis() const; + + protected: + NodeTableDef* mTableDef; + Node* mOnExpr; + QList mUsingColumns; + JoinType mType; + }; + + /** Column in a ORDER BY */ + class CORE_EXPORT NodeColumnSorted : public Node + { + public: + /** Constructor */ + NodeColumnSorted( NodeColumnRef* column, bool asc ) : mColumn( column ), mAsc( asc ) {} + ~NodeColumnSorted() { delete mColumn; } + + /** The name of the column. */ + NodeColumnRef* column() const { return mColumn; } + + /** Whether the column is sorted in ascending order */ + bool ascending() const { return mAsc; } + + virtual NodeType nodeType() const override { return ntColumnSorted; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + /** Clone with same type return */ + NodeColumnSorted* cloneThis() const; + + protected: + NodeColumnRef* mColumn; + bool mAsc; + }; + + /** SELECT node */ + class CORE_EXPORT NodeSelect : public Node + { + public: + /** Constructor */ + NodeSelect( QList tableList, QList columns, bool distinct ) : mTableList( tableList ), mColumns( columns ), mDistinct( distinct ), mWhere( nullptr ) {} + virtual ~NodeSelect() { qDeleteAll( mTableList ); qDeleteAll( mColumns ); qDeleteAll( mJoins ); delete mWhere; qDeleteAll( mOrderBy ); } + + /** Set joins */ + void setJoins( QList joins ) { qDeleteAll( mJoins ); mJoins = joins; } + /** Append a join */ + void appendJoin( NodeJoin* join ) { mJoins.append( join ); } + /** Set where clause */ + void setWhere( Node* where ) { delete mWhere; mWhere = where; } + /** Set order by columns */ + void setOrderBy( QList orderBy ) { qDeleteAll( mOrderBy ); mOrderBy = orderBy; } + + /** Return the list of tables */ + QList tables() const { return mTableList; } + /** Return the list of columns */ + QList columns() const { return mColumns; } + /** Return if the SELECT is DISTINCT */ + bool distinct() const { return mDistinct; } + /** Return the list of joins */ + QList joins() const { return mJoins; } + /** Return the where clause */ + Node* where() const { return mWhere; } + /** Return the list of order by columns */ + QList orderBy() const { return mOrderBy; } + + virtual NodeType nodeType() const override { return ntSelect; } + virtual QString dump() const override; + + virtual void accept( Visitor& v ) const override { v.visit( *this ); } + virtual Node* clone() const override; + + protected: + QList mTableList; + QList mColumns; + bool mDistinct; + QList mJoins; + Node* mWhere; + QList mOrderBy; + }; + + ////// + + /** Support for visitor pattern - algorithms dealing with the statement + may be implemented without modifying the Node classes */ + class CORE_EXPORT Visitor + { + public: + virtual ~Visitor() {} + /** Visit NodeUnaryOperator */ + virtual void visit( const NodeUnaryOperator& n ) = 0; + /** Visit NodeBinaryOperator */ + virtual void visit( const NodeBinaryOperator& n ) = 0; + /** Visit NodeInOperator */ + virtual void visit( const NodeInOperator& n ) = 0; + /** Visit NodeBetweenOperator */ + virtual void visit( const NodeBetweenOperator& n ) = 0; + /** Visit NodeFunction */ + virtual void visit( const NodeFunction& n ) = 0; + /** Visit NodeLiteral */ + virtual void visit( const NodeLiteral& n ) = 0; + /** Visit NodeColumnRef */ + virtual void visit( const NodeColumnRef& n ) = 0; + /** Visit NodeSelectedColumn */ + virtual void visit( const NodeSelectedColumn& n ) = 0; + /** Visit NodeTableDef */ + virtual void visit( const NodeTableDef& n ) = 0; + /** Visit NodeSelect */ + virtual void visit( const NodeSelect& n ) = 0; + /** Visit NodeJoin */ + virtual void visit( const NodeJoin& n ) = 0; + /** Visit NodeColumnSorted */ + virtual void visit( const NodeColumnSorted& n ) = 0; + /** Visit NodeCast */ + virtual void visit( const NodeCast& n ) = 0; + }; + + /** A visitor that recursively explores all children */ + class CORE_EXPORT RecursiveVisitor: public QgsSQLStatement::Visitor + { + public: + /** Constructor */ + RecursiveVisitor() {} + + void visit( const QgsSQLStatement::NodeUnaryOperator& n ) override { n.operand()->accept( *this ); } + void visit( const QgsSQLStatement::NodeBinaryOperator& n ) override { n.opLeft()->accept( *this ); n.opRight()->accept( *this ); } + void visit( const QgsSQLStatement::NodeInOperator& n ) override { n.node()->accept( *this ); n.list()->accept( *this ); } + void visit( const QgsSQLStatement::NodeBetweenOperator& n ) override { n.node()->accept( *this ); n.minVal()->accept( *this ); n.maxVal()->accept( *this ); } + void visit( const QgsSQLStatement::NodeFunction& n ) override { n.args()->accept( *this ); } + void visit( const QgsSQLStatement::NodeLiteral& ) override {} + void visit( const QgsSQLStatement::NodeColumnRef& ) override { } + void visit( const QgsSQLStatement::NodeSelectedColumn& n ) override { n.column()->accept( *this ); } + void visit( const QgsSQLStatement::NodeTableDef& ) override {} + void visit( const QgsSQLStatement::NodeSelect& n ) override; + void visit( const QgsSQLStatement::NodeJoin& n ) override; + void visit( const QgsSQLStatement::NodeColumnSorted& n ) override { n.column()->accept( *this ); } + void visit( const QgsSQLStatement::NodeCast& n ) override { n.node()->accept( *this ); } + }; + + /** Entry function for the visitor pattern */ + void acceptVisitor( Visitor& v ) const; + + protected: + QgsSQLStatement::Node* mRootNode; + QString mStatement; + QString mParserErrorString; +}; + +Q_DECLARE_METATYPE( QgsSQLStatement::Node* ) + +#endif // QGSSQLSTATEMENT_H diff --git a/src/core/qgssqlstatementlexer.ll b/src/core/qgssqlstatementlexer.ll new file mode 100644 index 00000000000..06746541dfb --- /dev/null +++ b/src/core/qgssqlstatementlexer.ll @@ -0,0 +1,198 @@ +/*************************************************************************** + qgssqlstatementlexer.ll + -------------------- + begin : April 2016 + copyright : (C) 2011 by Martin Dobias + copyright : (C) 2016 by Even Rouault + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +%option noyywrap +%option case-insensitive +%option never-interactive +%option nounput +%option prefix="sqlstatement_" + // this makes flex generate lexer with context + init/destroy functions +%option reentrant + // this makes Bison send yylex another argument to use instead of using the global variable yylval +%option bison-bridge + + // ensure that lexer will be 8-bit (and not just 7-bit) +%option 8bit + +%{ + +#include "qgssqlstatement.h" +struct sqlstatement_parser_context; +#include "qgssqlstatementparser.hpp" +#include + +// if not defined, searches for isatty() +// which doesn't in MSVC compiler +#define YY_NEVER_INTERACTIVE 1 + +#ifndef YY_NO_UNPUT +#define YY_NO_UNPUT // unused +#endif + +#ifdef _MSC_VER +#define YY_NO_UNISTD_H +#endif + +#define B_OP(x) yylval->b_op = QgsSQLStatement::x +#define U_OP(x) yylval->u_op = QgsSQLStatement::x +#define TEXT yylval->text = new QString( QString::fromUtf8(yytext) ); +#define TEXT_FILTER(filter_fn) yylval->text = new QString( filter_fn( QString::fromUtf8(yytext) ) ); + +static QString stripText(QString text) +{ + // strip single quotes on start,end + text = text.mid( 1, text.length() - 2 ); + + // make single "single quotes" from double "single quotes" + text.replace( "''", "'" ); + + // strip \n \' etc. + int index = 0; + while (( index = text.indexOf( '\\', index ) ) != -1 ) + { + text.remove( index, 1 ); // delete backslash + QChar chr; + switch ( text[index].toLatin1() ) // evaluate backslashed character + { + case 'n': chr = '\n'; break; + case 't': chr = '\t'; break; + case '\\': chr = '\\'; break; + case '\'': chr = '\''; break; + default: chr = '?'; break; + } + text[index++] = chr; // set new character and push index +1 + } + return text; +} + +// C locale for correct parsing of numbers even if the system locale is different +static QLocale cLocale("C"); + +%} + +white [ \t\r\n]+ + +non_ascii [\x80-\xFF] + +identifier_first [A-Za-z_]|{non_ascii} +identifier_next [A-Za-z0-9_]|{non_ascii} +identifier {identifier_first}{identifier_next}* + +identifier_str_char "\"\""|[^\"] +identifier_quoted "\""{identifier_str_char}*"\"" + +dig [0-9] +num_int [-]?{dig}+{identifier_first}* +num_float [-]?{dig}*(\.{dig}+([eE][-+]?{dig}+)?|[eE][-+]?{dig}+) +boolean "TRUE"|"FALSE" + +str_char ('')|(\\.)|[^'\\] +string "'"{str_char}*"'" + +%% + +"NOT" { U_OP(uoNot); return NOT; } +"AND" { B_OP(boAnd); return AND; } +"OR" { B_OP(boOr); return OR; } + +"=" { B_OP(boEQ); return EQ; } +"!=" { B_OP(boNE); return NE; } +"<=" { B_OP(boLE); return LE; } +">=" { B_OP(boGE); return GE; } +"<>" { B_OP(boNE); return NE; } +"<" { B_OP(boLT); return LT; } +">" { B_OP(boGT); return GT; } + +"LIKE" { B_OP(boLike); return LIKE; } +"NOT"{white}"LIKE" { B_OP(boNotLike); return LIKE; } +"ILIKE" { B_OP(boILike); return LIKE; } +"NOT"{white}"ILIKE" { B_OP(boNotILike); return LIKE; } +"IS" { B_OP(boIs); return IS; } +"IS"{white}"NOT" { B_OP(boIsNot); return IS; } +"||" { B_OP(boConcat); return CONCAT; } + +"+" { B_OP(boPlus); return PLUS; } +"-" { B_OP(boMinus); return MINUS; } +"*" { B_OP(boMul); return MUL_OR_STAR; } +"//" { B_OP(boIntDiv); return INTDIV; } +"/" { B_OP(boDiv); return DIV; } +"%" { B_OP(boMod); return MOD; } +"^" { B_OP(boPow); return POW; } + +"IN" { return IN; } +"BETWEEN" { return BETWEEN; } + +"NULL" { return NULLVALUE; } + +"SELECT" { return SELECT; } +"ALL" { return ALL; } +"DISTINCT" { return DISTINCT; } +"CAST" { return CAST; } +"AS" { return AS; } +"FROM" { return FROM; } +"JOIN" { return JOIN; } +"ON" { return ON; } +"USING" { return USING; } +"WHERE" { return WHERE; } +"ORDER" { return ORDER; } +"BY" { return BY; } +"ASC" { return ASC; } +"DESC" { return DESC; } +"LEFT" { return LEFT; } +"RIGHT" { return RIGHT; } +"INNER" { return INNER; } +"OUTER" { return OUTER; } +"CROSS" { return CROSS; } +"FULL" { return FULL; } +"NATURAL" { return NATURAL; } +"UNION" { return UNION; } + +[().] { return yytext[0]; } + +"," { return COMMA; } + +{num_float} { yylval->numberFloat = cLocale.toDouble( QString::fromAscii(yytext) ); return NUMBER_FLOAT; } +{num_int} { + bool ok; + yylval->numberInt = cLocale.toInt( QString::fromAscii(yytext), &ok ); + if( ok ) + return NUMBER_INT; + + yylval->numberInt64 = cLocale.toLongLong( QString::fromAscii(yytext), &ok ); + if( ok ) + return NUMBER_INT64; + + yylval->numberFloat = cLocale.toDouble( QString::fromAscii(yytext), &ok ); + if( ok ) + return NUMBER_FLOAT; + + return Unknown_CHARACTER; +} + +{boolean} { yylval->boolVal = QString( yytext ).compare( "true", Qt::CaseInsensitive ) == 0; return BOOLEAN; } + +{string} { TEXT_FILTER(stripText); return STRING; } + +{identifier} { TEXT; return IDENTIFIER; } + +{identifier_quoted} { TEXT_FILTER(QgsSQLStatement::stripQuotedIdentifier); return IDENTIFIER; } + +{white} /* skip blanks and tabs */ + +. { return Unknown_CHARACTER; } + + +%% diff --git a/src/core/qgssqlstatementparser.yy b/src/core/qgssqlstatementparser.yy new file mode 100644 index 00000000000..45711be2b72 --- /dev/null +++ b/src/core/qgssqlstatementparser.yy @@ -0,0 +1,633 @@ +/*************************************************************************** + qgssqlexpressionparser.yy + -------------------- + begin : April 2016 + copyright : (C) 2011 by Martin Dobias + copyright : (C) 2016 by Even Rouault + copyright : (C) 2010 by Frank Warmerdam (partly derived from GDAL ogr/swq_parser.y) + email : even.rouault at spatialys.com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +%{ +#include +#include +#include +#include "qgssqlstatement.h" + +#ifdef _MSC_VER +# pragma warning( disable: 4065 ) // switch statement contains 'default' but no 'case' labels +# pragma warning( disable: 4702 ) // unreachable code +#endif + +// don't redeclare malloc/free +#define YYINCLUDED_STDLIB_H 1 + +struct sqlstatement_parser_context; +#include "qgssqlstatementparser.hpp" + +//! from lexer +typedef void* yyscan_t; +typedef struct yy_buffer_state* YY_BUFFER_STATE; +extern int sqlstatement_lex_init(yyscan_t* scanner); +extern int sqlstatement_lex_destroy(yyscan_t scanner); +extern int sqlstatement_lex(YYSTYPE* yylval_param, yyscan_t yyscanner); +extern YY_BUFFER_STATE sqlstatement__scan_string(const char* buffer, yyscan_t scanner); + +/** returns parsed tree, otherwise returns nullptr and sets parserErrorMsg + (interface function to be called from QgsSQLStatement) + */ +QgsSQLStatement::Node* parse(const QString& str, QString& parserErrorMsg); + +/** error handler for bison */ +void sqlstatement_error(sqlstatement_parser_context* parser_ctx, const QString& errorMsg); + +struct sqlstatement_parser_context +{ + // lexer context + yyscan_t flex_scanner; + + // varible where the parser error will be stored + QString errorMsg; + // root node of the sqlstatement + QgsSQLStatement::NodeSelect* rootNode; + + QgsSQLStatement::Node* whereExp; + + QList joinList; + + QList orderByList; + + sqlstatement_parser_context() : rootNode( nullptr ), whereExp( nullptr ) {} + + void setWhere( QgsSQLStatement::Node* whereExp ) { this->whereExp = whereExp; } + + void setJoins( QList joinList ) { this->joinList = joinList; } + + void setOrderBy( QList orderByList ) { this->orderByList = orderByList; } +}; + +#define scanner parser_ctx->flex_scanner + +// we want verbose error messages +#define YYERROR_VERBOSE 1 + +#define BINOP(x, y, z) new QgsSQLStatement::NodeBinaryOperator(x, y, z) + +%} + +// make the parser reentrant +%define api.pure +%lex-param {void * scanner} +%parse-param {sqlstatement_parser_context* parser_ctx} + +%name-prefix "sqlstatement_" + +%union +{ + QgsSQLStatement::Node* node; + QgsSQLStatement::NodeColumnRef* nodecolumnref; + QgsSQLStatement::NodeSelectedColumn* nodeselectedcolumn; + QgsSQLStatement::NodeSelect* nodeselect; + QgsSQLStatement::NodeList* nodelist; + QgsSQLStatement::NodeJoin* nodejoin; + QgsSQLStatement::NodeTableDef* nodetabledef; + QgsSQLStatement::NodeColumnSorted* nodecolumnsorted; + QList* columnsortedlist; + QList* joinlist; + QList* tablelist; + QList* selectedcolumnlist; + double numberFloat; + int numberInt; + qlonglong numberInt64; + bool boolVal; + QString* text; + QgsSQLStatement::BinaryOperator b_op; + QgsSQLStatement::UnaryOperator u_op; + QgsSQLStatement::JoinType jointype; + QList* usinglist; +} + +%start root + + +// +// token definitions +// + +// operator tokens +%token OR AND EQ NE LE GE LT GT LIKE IS PLUS MINUS MUL_OR_STAR DIV INTDIV MOD CONCAT POW +%token NOT +%token IN BETWEEN + +%token SELECT ALL DISTINCT CAST AS JOIN FROM ON USING WHERE ORDER BY ASC DESC LEFT RIGHT INNER OUTER CROSS FULL NATURAL UNION + +// literals +%token NUMBER_FLOAT +%token NUMBER_INT +%token NUMBER_INT64 +%token BOOLEAN +%token NULLVALUE + +%token STRING IDENTIFIER + +%token COMMA + +%token Unknown_CHARACTER + +// +// definition of non-terminal types +// + +%type expr +%type expr_non_logical +%type column_name +%type sort_spec +%type selected_column +%type select_statement +%type join +%type table_def +%type expr_list +%type selected_column_list +%type join_list +%type table_list +%type sort_spec_list +%type using_list; +%type as_clause +%type join_qualifier +%type select_type; + +// debugging +%error-verbose + +// +// operator precedence +// + +// left associativity means that 1+2+3 translates to (1+2)+3 +// the order of operators here determines their precedence + +%left OR +%left AND +%right NOT +%left BETWEEN +%left EQ NE LE GE LT GT LIKE IS IN +%left PLUS MINUS +%left MUL_OR_STAR DIV INTDIV MOD +%right POW +%left CONCAT + +%right UMINUS // fictitious symbol (for unary minus) + +%left COMMA + +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { delete $$; } +%destructor { qDeleteAll(*$$); delete $$; } +%destructor { qDeleteAll(*$$); delete $$; } +%destructor { qDeleteAll(*$$); delete $$; } +%destructor { qDeleteAll(*$$); delete $$; } + +%% + +root: select_statement { parser_ctx->rootNode = $1; } + ; + +/* We have to separate expr from expr_non_logical to avoid */ +/* grammar ambiguities with the AND of the "BETWEEN x AND y" and the */ +/* logical binary AND */ +expr: + expr_non_logical + { + $$ = $1; + } + + | expr AND expr { $$ = BINOP($2, $1, $3); } + | expr OR expr { $$ = BINOP($2, $1, $3); } + | expr EQ expr { $$ = BINOP($2, $1, $3); } + | expr NE expr { $$ = BINOP($2, $1, $3); } + | expr LE expr { $$ = BINOP($2, $1, $3); } + | expr GE expr { $$ = BINOP($2, $1, $3); } + | expr LT expr { $$ = BINOP($2, $1, $3); } + | expr GT expr { $$ = BINOP($2, $1, $3); } + | expr LIKE expr { $$ = BINOP($2, $1, $3); } + | expr IS expr { $$ = BINOP($2, $1, $3); } + | NOT expr { $$ = new QgsSQLStatement::NodeUnaryOperator($1, $2); } + + | expr IN '(' expr_list ')' { $$ = new QgsSQLStatement::NodeInOperator($1, $4, false); } + | expr NOT IN '(' expr_list ')' { $$ = new QgsSQLStatement::NodeInOperator($1, $5, true); } + + | expr BETWEEN expr_non_logical AND expr_non_logical { $$ = new QgsSQLStatement::NodeBetweenOperator($1, $3, $5, false); } + | expr NOT BETWEEN expr_non_logical AND expr_non_logical { $$ = new QgsSQLStatement::NodeBetweenOperator($1, $4, $6, true); } + +; + +column_name: + IDENTIFIER + { + $$ = new QgsSQLStatement::NodeColumnRef( *$1, false ); + delete $1; + } + + | IDENTIFIER '.' IDENTIFIER + { + $$ = new QgsSQLStatement::NodeColumnRef( *$1, *$3, false ); + delete $1; + delete $3; + } +; + +expr_list: + expr_list COMMA expr + { + $$ = $1; $1->append($3); + } + | expr { $$ = new QgsSQLStatement::NodeList(); $$->append($1); } +; + +expr_non_logical: + + // literals + NUMBER_FLOAT { $$ = new QgsSQLStatement::NodeLiteral( QVariant($1) ); } + | NUMBER_INT { $$ = new QgsSQLStatement::NodeLiteral( QVariant($1) ); } + | NUMBER_INT64 { $$ = new QgsSQLStatement::NodeLiteral( QVariant($1) ); } + | BOOLEAN { $$ = new QgsSQLStatement::NodeLiteral( QVariant($1) ); } + | STRING { $$ = new QgsSQLStatement::NodeLiteral( QVariant(*$1) ); delete $1; } + | NULLVALUE { $$ = new QgsSQLStatement::NodeLiteral( QVariant() ); } + + | column_name + { + $$ = $1; + } + + | '(' expr ')' { $$ = $2; } + + | IDENTIFIER '(' expr_list ')' + { + $$ = new QgsSQLStatement::NodeFunction(*$1, $3); + delete $1; + } + + | IDENTIFIER '(' ')' + { + $$ = new QgsSQLStatement::NodeFunction(*$1, new QgsSQLStatement::NodeList()); + delete $1; + } + + | expr_non_logical PLUS expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical MINUS expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical MUL_OR_STAR expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical INTDIV expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical DIV expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical MOD expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical POW expr_non_logical { $$ = BINOP($2, $1, $3); } + | expr_non_logical CONCAT expr_non_logical { $$ = BINOP($2, $1, $3); } + | PLUS expr_non_logical %prec UMINUS { $$ = $2; } + | MINUS expr_non_logical %prec UMINUS { $$ = new QgsSQLStatement::NodeUnaryOperator( QgsSQLStatement::uoMinus, $2); } + + | CAST '(' expr AS IDENTIFIER ')' + { + $$ = new QgsSQLStatement::NodeCast($3, *$5); + delete $5; + } + +; + +select_type: + SELECT + { + $$ = false; + } + | SELECT ALL + { + $$ = false; + } + | SELECT DISTINCT + { + $$ = true; + } +; + +select_statement: + select_type selected_column_list FROM table_list opt_joins opt_where opt_order_by + { + $$ = new QgsSQLStatement::NodeSelect(*$4, *$2, $1); + delete $2; + delete $4; + } +; + +selected_column_list: + selected_column_list COMMA selected_column + { + $$ = $1; $1->append($3); + } + | selected_column + { $$ = new QList(); $$->append($1); } +; + +selected_column: + expr + { + $$ = new QgsSQLStatement::NodeSelectedColumn($1); + } + + | expr as_clause + { + $$ = new QgsSQLStatement::NodeSelectedColumn($1); + $$->setAlias(*$2); + delete $2; + } + + | MUL_OR_STAR + { + $$ = new QgsSQLStatement::NodeSelectedColumn( new QgsSQLStatement::NodeColumnRef("*", true) ); + } + + | IDENTIFIER '.' MUL_OR_STAR + { + $$ = new QgsSQLStatement::NodeSelectedColumn( new QgsSQLStatement::NodeColumnRef(*$1, "*", true) ); + delete $1; + } + + | IDENTIFIER '(' MUL_OR_STAR ')' + { + // special case for COUNT(*), confirm it. + if( $1->compare("COUNT", Qt::CaseInsensitive) != 0 ) + { + sqlstatement_error(parser_ctx, QString( QObject::tr("Syntax Error with %1(*).") ).arg(*$1)); + delete $1; + YYERROR; + } + delete $1; + QgsSQLStatement::NodeList* nodeList = new QgsSQLStatement::NodeList(); + nodeList->append( new QgsSQLStatement::NodeColumnRef("*", true) ); + $$ = new QgsSQLStatement::NodeSelectedColumn( + new QgsSQLStatement::NodeFunction( "COUNT", nodeList) ); + } + + | IDENTIFIER '(' MUL_OR_STAR ')' as_clause + { + // special case for COUNT(*), confirm it. + if( $1->compare("COUNT", Qt::CaseInsensitive) != 0 ) + { + sqlstatement_error(parser_ctx, QString( QObject::tr("Syntax Error with %1(*).") ).arg(*$1)); + delete $1; + delete $5; + YYERROR; + } + delete $1; + QgsSQLStatement::NodeList* nodeList = new QgsSQLStatement::NodeList(); + nodeList->append( new QgsSQLStatement::NodeColumnRef("*", true) ); + $$ = new QgsSQLStatement::NodeSelectedColumn( + new QgsSQLStatement::NodeFunction( "COUNT", nodeList) ); + $$->setAlias(*$5); + delete $5; + } + + | IDENTIFIER '(' DISTINCT column_name ')' + { + // special case for COUNT(DISTINCT x), confirm it. + if( $1->compare("COUNT", Qt::CaseInsensitive) != 0 ) + { + sqlstatement_error(parser_ctx, QObject::tr( + "DISTINCT keyword can only be used in COUNT() operator.") ); + delete $1; + delete $4; + YYERROR; + } + delete $1; + QgsSQLStatement::NodeList* nodeList = new QgsSQLStatement::NodeList(); + $4->setDistinct(); + nodeList->append( $4 ); + $$ = new QgsSQLStatement::NodeSelectedColumn( + new QgsSQLStatement::NodeFunction( "COUNT", nodeList) ); + } + + | IDENTIFIER '(' DISTINCT column_name ')' as_clause + { + // special case for COUNT(DISTINCT x), confirm it. + if( $1->compare("COUNT", Qt::CaseInsensitive) != 0 ) + { + sqlstatement_error(parser_ctx, QObject::tr( + "DISTINCT keyword can only be used in COUNT() operator.") ); + delete $1; + delete $4; + delete $6; + YYERROR; + } + delete $1; + QgsSQLStatement::NodeList* nodeList = new QgsSQLStatement::NodeList(); + $4->setDistinct(); + nodeList->append( $4 ); + $$ = new QgsSQLStatement::NodeSelectedColumn( + new QgsSQLStatement::NodeFunction( "COUNT", nodeList) ); + $$->setAlias(*$6); + delete $6; + } +; + +as_clause: + AS IDENTIFIER + { + $$ = $2; + } + + | IDENTIFIER +; + +opt_where: + | WHERE expr + { + parser_ctx->setWhere($2); + } +; + +join_qualifier: + JOIN + { + $$ = QgsSQLStatement::jtDefault; + } + | LEFT JOIN + { + $$ = QgsSQLStatement::jtLeft; + } + | LEFT OUTER JOIN + { + $$ = QgsSQLStatement::jtLeftOuter; + } + | RIGHT JOIN + { + $$ = QgsSQLStatement::jtRight; + } + | RIGHT OUTER JOIN + { + $$ = QgsSQLStatement::jtRightOuter; + } + | FULL JOIN + { + $$ = QgsSQLStatement::jtFull; + } + | CROSS JOIN + { + $$ = QgsSQLStatement::jtCross; + } + | INNER JOIN + { + $$ = QgsSQLStatement::jtInner; + } +; + +join: + join_qualifier table_def ON expr + { + $$ = new QgsSQLStatement::NodeJoin($2, $4, $1); + } + | join_qualifier table_def USING '(' using_list ')' + { + $$ = new QgsSQLStatement::NodeJoin($2, *$5, $1); + delete $5; + } +; + +using_list: + IDENTIFIER + { + $$ = new QList(); $$->push_back(*$1); + delete $1; + } + | using_list COMMA IDENTIFIER + { + $$ = $1; $1->push_back(*$3); + delete $3; + } +; + +join_list: + join + { + $$ = new QList(); $$->push_back($1); + } + | join_list join + { + $$ = $1; $1->push_back($2); + } +; + +opt_joins: + | join_list + { + parser_ctx->setJoins( *$1 ); + delete $1; + } +; + +opt_order_by: + | ORDER BY sort_spec_list + { + parser_ctx->setOrderBy(*$3); + delete $3; + } +; + +sort_spec_list: + sort_spec_list COMMA sort_spec + { + $$ = $1; $1->push_back($3); + } + | sort_spec + { $$ = new QList(); $$->push_back($1); } +; + +sort_spec: + column_name + { + $$ = new QgsSQLStatement::NodeColumnSorted( $1, true ); + } + | column_name ASC + { + $$ = new QgsSQLStatement::NodeColumnSorted( $1, true ); + } + | column_name DESC + { + $$ = new QgsSQLStatement::NodeColumnSorted( $1, false ); + } +; + +table_def: + IDENTIFIER + { + $$ = new QgsSQLStatement::NodeTableDef(*$1); + delete $1; + } + + | IDENTIFIER as_clause + { + $$ = new QgsSQLStatement::NodeTableDef(*$1, *$2); + delete $1; + delete $2; + } +; + +table_list: + table_list COMMA table_def + { + $$ = $1; $1->push_back($3); + } + | table_def + { $$ = new QList(); $$->push_back($1); } +; + +%% + + +// returns parsed tree, otherwise returns nullptr and sets parserErrorMsg +QgsSQLStatement::Node* parse(const QString& str, QString& parserErrorMsg) +{ + sqlstatement_parser_context ctx; + ctx.rootNode = 0; + + sqlstatement_lex_init(&ctx.flex_scanner); + sqlstatement__scan_string(str.toUtf8().constData(), ctx.flex_scanner); + int res = sqlstatement_parse(&ctx); + sqlstatement_lex_destroy(ctx.flex_scanner); + + // list should be empty when parsing was OK + if (res == 0) // success? + { + ctx.rootNode->setWhere(ctx.whereExp); + ctx.rootNode->setJoins(ctx.joinList); + ctx.rootNode->setOrderBy(ctx.orderByList); + return ctx.rootNode; + } + else // error? + { + parserErrorMsg = ctx.errorMsg; + delete ctx.rootNode; + delete ctx.whereExp; + qDeleteAll(ctx.joinList); + qDeleteAll(ctx.orderByList); + return nullptr; + } +} + + +void sqlstatement_error(sqlstatement_parser_context* parser_ctx, const QString& errorMsg) +{ + parser_ctx->errorMsg = errorMsg; +} + diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 629a400a2f1..495d698aabb 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -68,6 +68,7 @@ ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py) ADD_PYTHON_TEST(PyQgsOGRProvider test_provider_ogr.py) ADD_PYTHON_TEST(PyQgsSpatialIndex test_qgsspatialindex.py) ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_provider_spatialite.py) +ADD_PYTHON_TEST(PyQgsSQLStatement test_qgssqlstatement.py) ADD_PYTHON_TEST(PyQgsStringStatisticalSummary test_qgsstringstatisticalsummary.py) ADD_PYTHON_TEST(PyQgsSymbolLayerV2 test_qgssymbollayerv2.py) ADD_PYTHON_TEST(PyQgsArrowSymbolLayer test_qgsarrowsymbollayer.py) diff --git a/tests/src/python/test_qgssqlstatement.py b/tests/src/python/test_qgssqlstatement.py new file mode 100644 index 00000000000..bf6d1b44e1a --- /dev/null +++ b/tests/src/python/test_qgssqlstatement.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsSQLStatement. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Even Rouault' +__date__ = '4/4/2016' +__copyright__ = 'Copyright 2016, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +from qgis.testing import unittest +from qgis.core import QgsSQLStatement + + +class TestQgsSQLStatementCustomFunctions(unittest.TestCase): + + def checkNominal(self, statement, expected_dump=None): + exp = QgsSQLStatement(statement) + self.assertEqual(exp.hasParserError(), False) + self.assertEqual(exp.parserErrorString(), "") + if expected_dump is None: + expected_dump = statement + self.assertEqual(exp.dump(), expected_dump) + self.assertEqual(exp.dump(), exp.rootNode().clone().dump()) + + def testNominalSimple(self): + statement = "SELECT a FROM t" + self.checkNominal(statement) + exp = QgsSQLStatement(statement) + statement_node = exp.rootNode() + self.assertEqual(statement_node.nodeType(), QgsSQLStatement.ntSelect) + tables = statement_node.tables() + self.assertEqual(len(tables), 1) + table = tables[0] + self.assertEqual(table.nodeType(), QgsSQLStatement.ntTableDef) + self.assertEqual(table.name(), 't') + self.assertEqual(table.alias(), '') + columns = statement_node.columns() + self.assertEqual(len(columns), 1) + column = columns[0] + self.assertEqual(column.nodeType(), QgsSQLStatement.ntSelectedColumn) + column_ref = column.column() + self.assertEqual(column.alias(), '') + self.assertEqual(column_ref.nodeType(), QgsSQLStatement.ntColumnRef) + self.assertEqual(column_ref.name(), 'a') + self.assertEqual(column_ref.tableName(), '') + + def testNominalSelectDistinct(self): + statement = "SELECT DISTINCT a FROM t" + self.checkNominal(statement) + + def testNominalColumns(self): + statement = "SELECT null, 1234567890123456789, a, b b_alias, 'literal', CAST(1 AS varchar), " + statement += "\"1c\", *, \"*\", a.*, foo(), bar(baz, baw), t.c AS \"1quoted\", " + statement += "COUNT(*), COUNT(*) a, COUNT(DISTINCT x), COUNT(DISTINCT x) AS a FROM t" + expected_dump = "SELECT NULL, 1234567890123456789, a, b AS b_alias, 'literal', CAST(1 AS varchar), " + expected_dump += "\"1c\", *, \"*\", a.*, foo(), bar(baz, baw), t.c AS \"1quoted\", " + expected_dump += "COUNT(*), COUNT(*) AS a, COUNT(DISTINCT x), COUNT(DISTINCT x) AS a FROM t" + self.checkNominal(statement, expected_dump) + + def testNominalFrom(self): + statement = "SELECT a FROM t1, t2 at2, t3 AS at3, \"1quoted\", t4 AS \"2quoted\"" + expected_dump = "SELECT a FROM t1, t2 AS at2, t3 AS at3, \"1quoted\", t4 AS \"2quoted\"" + self.checkNominal(statement, expected_dump) + + def testNominalWhere(self): + statement = "SELECT a FROM t WHERE 1.5 <= 'a' OR TRUE OR FALSE OR a IS NULL AND b IS NOT NULL " + \ + "OR NOT d OR 1 + (2 - 3) * 4 / 5 ^ 6 <> 0 OR a IN (1, 2) OR b NOT IN (5) " + \ + "OR x BETWEEN 5 AND 6 OR x NOT BETWEEN 5 AND 6 OR c = d OR c > d OR c < d OR c >= d OR c <= d" + self.checkNominal(statement) + + def checkJoinType(self, joinType): + statement = "SELECT a FROM t " + joinType + " j1 ON TRUE" + self.checkNominal(statement) + + def testJoinTypes(self): + self.checkJoinType('JOIN') + self.checkJoinType('LEFT JOIN') + self.checkJoinType('LEFT OUTER JOIN') + self.checkJoinType('RIGHT JOIN') + self.checkJoinType('RIGHT OUTER JOIN') + self.checkJoinType('CROSS JOIN') + self.checkJoinType('FULL JOIN') + self.checkJoinType('INNER JOIN') + + def testJoin(self): + statement = "SELECT a FROM t JOIN j1 ON TRUE JOIN j2 USING (a) JOIN j3 USING (\"1a\", b)" + self.checkNominal(statement) + + def testNominalOrderBy(self): + statement = "SELECT a FROM t ORDER BY a, b ASC, c DESC" + expected_dump = "SELECT a FROM t ORDER BY a, b, c DESC" + self.checkNominal(statement, expected_dump) + + def testNominalFull(self): + statement = \ + "SELECT a FROM t JOIN j1 ON cond1 JOIN j2 ON cond2 WHERE TRUE ORDER BY c" + self.checkNominal(statement) + + def checkError(self, statement): + exp = QgsSQLStatement(statement) + self.assertEqual(exp.hasParserError(), True) + self.assertNotEqual(exp.parserErrorString(), '') + self.assertEqual(exp.dump(), "(no root)") + self.assertEqual(exp.rootNode(), None) + + def testError(self): + self.checkError("1") + self.checkError("SELECT") + self.checkError("SELECT a") + self.checkError("SELECT a, FROM b") + self.checkError("SELECT 1a FROM b") + self.checkError("SELECT a AS FROM b") + self.checkError("SELECT a,. FROM b") + self.checkError("SELECT f(*) FROM b") + self.checkError("SELECT f(*) a FROM b") + self.checkError("SELECT .") + self.checkError("SELECT a FROM") + self.checkError("SELECT a FROM b WHERE") + self.checkError("SELECT a FROM b WHERE .") + self.checkError("SELECT a FROM b,") + self.checkError("SELECT a FROM b,.") + self.checkError("SELECT a FROM b JOIN") + self.checkError("SELECT a FROM b JOIN c") + self.checkError("SELECT a FROM b JOIN c ON") + self.checkError("SELECT a FROM b JOIN c USING") + self.checkError("SELECT a FROM b JOIN c ON d JOIN") + self.checkError("SELECT a FROM b ORDER BY") + self.checkError("SELECT a FROM b JOIN c ON d ORDER BY e unexpected") + + def testBasicValidationCheck(self): + exp = QgsSQLStatement("error") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual(errorMsg, 'No root node') + + exp = QgsSQLStatement("SELECT c FROM t") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertTrue(b) + self.assertEqual(errorMsg, '') + + exp = QgsSQLStatement("SELECT t.c FROM t ORDER BY t.c") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertTrue(b) + self.assertEqual(errorMsg, '') + + exp = QgsSQLStatement("SELECT t.c FROM t t_alias") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual( + errorMsg, 'Table t is referenced by column c, but not selected in FROM / JOIN.') + + exp = QgsSQLStatement( + "SELECT CAST(1 + foo(t_unknown.a) AS varchar) FROM t") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual( + errorMsg, 'Table t_unknown is referenced by column a, but not selected in FROM / JOIN.') + + exp = QgsSQLStatement("SELECT c FROM t WHERE t_unknown.a = 1") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual( + errorMsg, 'Table t_unknown is referenced by column a, but not selected in FROM / JOIN.') + + exp = QgsSQLStatement( + "SELECT c FROM t JOIN t2 ON t.c1 = t2.c2 AND t3.c3 IS NOT NULL") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual( + errorMsg, 'Table t3 is referenced by column c3, but not selected in FROM / JOIN.') + + exp = QgsSQLStatement("SELECT c FROM t ORDER BY t_unknown.c") + (b, errorMsg) = exp.doBasicValidationChecks() + self.assertFalse(b) + self.assertEqual( + errorMsg, 'Table t_unknown is referenced by column c, but not selected in FROM / JOIN.') + +if __name__ == "__main__": + unittest.main()