Add QgsSQLStatement class to parse SELECT statements

This commit is contained in:
Even Rouault 2016-04-19 21:52:53 +02:00
parent 12a630f050
commit 9df19e0133
9 changed files with 3054 additions and 3 deletions

View File

@ -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

View File

@ -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<QgsSQLStatement::Node*>& 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<QString> 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<QString> 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<QgsSQLStatement::NodeTableDef*> tableList /Transfer/, QList<QgsSQLStatement::NodeSelectedColumn*> columns /Transfer/, bool distinct );
/** Set joins */
void setJoins( QList<QgsSQLStatement::NodeJoin*> 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<QgsSQLStatement::NodeColumnSorted*> orderBy /Transfer/ );
/** Return the list of tables */
QList<QgsSQLStatement::NodeTableDef*> tables() const;
/** Return the list of columns */
QList<QgsSQLStatement::NodeSelectedColumn*> columns() const;
/** Return if the SELECT is DISTINCT */
bool distinct() const;
/** Return the list of joins */
QList<QgsSQLStatement::NodeJoin*> joins() const;
/** Return the where clause */
QgsSQLStatement::Node* where() const;
/** Return the list of order by columns */
QList<QgsSQLStatement::NodeColumnSorted*> 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;
};

View File

@ -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(

View File

@ -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 <math.h>
#include <limits>
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<QString, QString> TableColumnPair;
QgsSQLStatementCollectTableNames() {}
void visit( const QgsSQLStatement::NodeColumnRef& n ) override;
void visit( const QgsSQLStatement::NodeTableDef& n ) override;
QSet<QString> tableNamesDeclared;
QSet<TableColumnPair> 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<QgsSQLStatement::NodeBinaryOperator *>( mOpLeft );
QgsSQLStatement::NodeBinaryOperator *rOp = dynamic_cast<QgsSQLStatement::NodeBinaryOperator *>( mOpRight );
QgsSQLStatement::NodeUnaryOperator *ruOp = dynamic_cast<QgsSQLStatement::NodeUnaryOperator *>( 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<QgsSQLStatement::NodeSelectedColumn*> newColumnList;
Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, mColumns )
{
newColumnList.push_back( column->cloneThis() );
}
QList<QgsSQLStatement::NodeTableDef*> 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<QgsSQLStatement::NodeColumnSorted*> 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 );
}

740
src/core/qgssqlstatement.h Normal file
View File

@ -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 <QCoreApplication>
#include <QMetaType>
#include <QStringList>
#include <QVariant>
#include <QList>
#include <QSet>
/**
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<Node*> 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<Node*> 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<QString> 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<QString> 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<QString> 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<NodeTableDef*> tableList, QList<NodeSelectedColumn*> 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<NodeJoin*> 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<NodeColumnSorted*> orderBy ) { qDeleteAll( mOrderBy ); mOrderBy = orderBy; }
/** Return the list of tables */
QList<NodeTableDef*> tables() const { return mTableList; }
/** Return the list of columns */
QList<NodeSelectedColumn*> columns() const { return mColumns; }
/** Return if the SELECT is DISTINCT */
bool distinct() const { return mDistinct; }
/** Return the list of joins */
QList<NodeJoin*> joins() const { return mJoins; }
/** Return the where clause */
Node* where() const { return mWhere; }
/** Return the list of order by columns */
QList<NodeColumnSorted*> 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<NodeTableDef*> mTableList;
QList<NodeSelectedColumn*> mColumns;
bool mDistinct;
QList<NodeJoin*> mJoins;
Node* mWhere;
QList<NodeColumnSorted*> 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

View File

@ -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 <QLocale>
// 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; }
%%

View File

@ -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 <qglobal.h>
#include <QList>
#include <cstdlib>
#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<QgsSQLStatement::NodeJoin*> joinList;
QList<QgsSQLStatement::NodeColumnSorted*> orderByList;
sqlstatement_parser_context() : rootNode( nullptr ), whereExp( nullptr ) {}
void setWhere( QgsSQLStatement::Node* whereExp ) { this->whereExp = whereExp; }
void setJoins( QList<QgsSQLStatement::NodeJoin*> joinList ) { this->joinList = joinList; }
void setOrderBy( QList<QgsSQLStatement::NodeColumnSorted*> 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<QgsSQLStatement::NodeColumnSorted*>* columnsortedlist;
QList<QgsSQLStatement::NodeJoin*>* joinlist;
QList<QgsSQLStatement::NodeTableDef*>* tablelist;
QList<QgsSQLStatement::NodeSelectedColumn*>* selectedcolumnlist;
double numberFloat;
int numberInt;
qlonglong numberInt64;
bool boolVal;
QString* text;
QgsSQLStatement::BinaryOperator b_op;
QgsSQLStatement::UnaryOperator u_op;
QgsSQLStatement::JoinType jointype;
QList<QString>* usinglist;
}
%start root
//
// token definitions
//
// operator tokens
%token <b_op> OR AND EQ NE LE GE LT GT LIKE IS PLUS MINUS MUL_OR_STAR DIV INTDIV MOD CONCAT POW
%token <u_op> 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 <numberFloat> NUMBER_FLOAT
%token <numberInt> NUMBER_INT
%token <numberInt64> NUMBER_INT64
%token <boolVal> BOOLEAN
%token NULLVALUE
%token <text> STRING IDENTIFIER
%token COMMA
%token Unknown_CHARACTER
//
// definition of non-terminal types
//
%type <node> expr
%type <node> expr_non_logical
%type <nodecolumnref> column_name
%type <nodecolumnsorted> sort_spec
%type <nodeselectedcolumn> selected_column
%type <nodeselect> select_statement
%type <nodejoin> join
%type <nodetabledef> table_def
%type <nodelist> expr_list
%type <selectedcolumnlist> selected_column_list
%type <joinlist> join_list
%type <tablelist> table_list
%type <columnsortedlist> sort_spec_list
%type <usinglist> using_list;
%type <text> as_clause
%type <jointype> join_qualifier
%type <boolVal> 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 $$; } <node>
%destructor { delete $$; } <nodelist>
%destructor { delete $$; } <nodecolumnref>
%destructor { delete $$; } <text>
%destructor { delete $$; } <nodetabledef>
%destructor { delete $$; } <nodeselect>
%destructor { delete $$; } <nodeselectedcolumn>
%destructor { delete $$; } <nodecolumnsorted>
%destructor { delete $$; } <nodejoin>
%destructor { delete $$; } <usinglist>
%destructor { qDeleteAll(*$$); delete $$; } <selectedcolumnlist>
%destructor { qDeleteAll(*$$); delete $$; } <columnsortedlist>
%destructor { qDeleteAll(*$$); delete $$; } <joinlist>
%destructor { qDeleteAll(*$$); delete $$; } <tablelist>
%%
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<QgsSQLStatement::NodeSelectedColumn*>(); $$->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<QString>(); $$->push_back(*$1);
delete $1;
}
| using_list COMMA IDENTIFIER
{
$$ = $1; $1->push_back(*$3);
delete $3;
}
;
join_list:
join
{
$$ = new QList<QgsSQLStatement::NodeJoin*>(); $$->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<QgsSQLStatement::NodeColumnSorted*>(); $$->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<QgsSQLStatement::NodeTableDef*>(); $$->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;
}

View File

@ -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)

View File

@ -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()