mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-13 00:03:09 -04:00
Add QgsSQLStatement class to parse SELECT statements
This commit is contained in:
parent
12a630f050
commit
9df19e0133
@ -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
|
||||
|
602
python/core/qgssqlstatement.sip
Normal file
602
python/core/qgssqlstatement.sip
Normal 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;
|
||||
};
|
@ -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(
|
||||
|
691
src/core/qgssqlstatement.cpp
Normal file
691
src/core/qgssqlstatement.cpp
Normal 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
740
src/core/qgssqlstatement.h
Normal 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
|
198
src/core/qgssqlstatementlexer.ll
Normal file
198
src/core/qgssqlstatementlexer.ll
Normal 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; }
|
||||
|
||||
|
||||
%%
|
633
src/core/qgssqlstatementparser.yy
Normal file
633
src/core/qgssqlstatementparser.yy
Normal 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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
184
tests/src/python/test_qgssqlstatement.py
Normal file
184
tests/src/python/test_qgssqlstatement.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user