QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgssqlstatement.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgssqlstatement.cpp
3  -------------------
4  begin : April 2016
5  copyright : (C) 2011 by Martin Dobias
6  copyright : (C) 2016 by Even Rouault
7  email : even.rouault at spatialys.com
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgssqlstatement.h"
18 
19 #include <math.h>
20 #include <limits>
21 
22 static const QRegExp identifierRE( "^[A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*$" );
23 
24 // from parser
25 extern QgsSQLStatement::Node* parse( const QString& str, QString& parserErrorMsg );
26 
28 // operators
29 
31 {
32  // this must correspond (number and order of element) to the declaration of the enum BinaryOperator
33  "OR", "AND",
34  "=", "<>", "<=", ">=", "<", ">", "LIKE", "NOT LIKE", "ILIKE", "NOT ILIKE", "IS", "IS NOT",
35  "+", "-", "*", "/", "//", "%", "^",
36  "||"
37 };
38 
40 {
41  // this must correspond (number and order of element) to the declaration of the enum UnaryOperator
42  "NOT", "-"
43 };
44 
45 const char* QgsSQLStatement::JoinTypeText[] =
46 {
47  // this must correspond (number and order of element) to the declaration of the enum JoinType
48  "", "LEFT", "LEFT OUTER", "RIGHT", "RIGHT OUTER", "CROSS", "INNER", "FULL"
49 };
50 
52 
54 {
55  if ( !mStatement.isNull() )
56  return mStatement;
57  else
58  return dump();
59 }
60 
62 {
63  if ( !mRootNode )
64  return tr( "(no root)" );
65 
66  return mRootNode->dump();
67 }
68 
70 {
71  return QString( "\"%1\"" ).arg( name.replace( '\"', "\"\"" ) );
72 }
73 
75 {
76  // This might not be complete, but it must be at least what we recognize
77  static const char* const reservedKeyWords[] =
78  {
79  "AND", "OR", "NOT", "LIKE", "IN", "IS", "BETWEEN", "NULL", "SELECT", "ALL", "DISTINCT", "CAST", "AS",
80  "FROM", "JOIN", "ON", "USING", "WHERE", "ORDER", "BY", "ASC", "DESC",
81  "LEFT", "RIGHT", "INNER", "OUTER", "CROSS", "FULL", "NATURAL", "UNION",
82  "OFFSET", "LIMIT", "GROUP", "HAVING"
83  };
84 
85  for ( size_t i = 0; i < sizeof( reservedKeyWords ) / sizeof( reservedKeyWords[0] ); ++i )
86  {
87  if ( name.compare( QString( reservedKeyWords[i] ), Qt::CaseInsensitive ) == 0 )
88  {
89  return quotedIdentifier( name );
90  }
91  }
92  return identifierRE.exactMatch( name ) ? name : quotedIdentifier( name );
93 }
94 
96 {
97  if ( text.length() >= 2 && text[0] == '"' && text[text.length()-1] == '"' )
98  {
99  // strip double quotes on start,end
100  text = text.mid( 1, text.length() - 2 );
101 
102  // make single "double quotes" from double "double quotes"
103  text.replace( "\"\"", "\"" );
104  }
105  return text;
106 }
107 
109 {
110  text.replace( '\'', "''" );
111  text.replace( '\\', "\\\\" );
112  text.replace( '\n', "\\n" );
113  text.replace( '\t', "\\t" );
114  return QString( "'%1'" ).arg( text );
115 }
116 
118 {
120  mStatement = expr;
121 }
122 
124 {
126  mStatement = other.mStatement;
127 }
128 
130 {
131  if ( &other != this )
132  {
133  delete mRootNode;
136  mStatement = other.mStatement;
137  }
138  return *this;
139 }
140 
142 {
143  delete mRootNode;
144 }
145 
147 
149 
151 {
152  if ( mRootNode )
153  mRootNode->accept( v );
154 }
155 
157 {
158  return mRootNode;
159 }
160 
162 {
163  Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, n.tables() )
164  {
165  table->accept( *this );
166  }
167  Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, n.columns() )
168  {
169  column->accept( *this );
170  }
171  Q_FOREACH ( QgsSQLStatement::NodeJoin* join, n.joins() )
172  {
173  join->accept( *this );
174  }
175  QgsSQLStatement::Node* where = n.where();
176  if ( where )
177  where->accept( *this );
178  Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* column, n.orderBy() )
179  {
180  column->accept( *this );
181  }
182 }
183 
185 {
186  n.tableDef()->accept( *this );
187  QgsSQLStatement::Node* expr = n.onExpr();
188  if ( expr )
189  expr->accept( *this );
190 }
191 
197 {
198  public:
200 
202 
203  void visit( const QgsSQLStatement::NodeColumnRef& n ) override;
204  void visit( const QgsSQLStatement::NodeTableDef& n ) override;
205 
208 };
209 
211 {
212  if ( !n.tableName().isEmpty() )
213  tableNamesReferenced.insert( TableColumnPair( n.tableName(), n.name() ) );
215 }
216 
218 {
219  tableNamesDeclared.insert( n.alias().isEmpty() ? n.name() : n.alias() );
221 }
222 
224 {
225  errorMsgOut.clear();
226  if ( mRootNode == nullptr )
227  {
228  errorMsgOut = tr( "No root node" );
229  return false;
230  }
232  mRootNode->accept( v );
233 
235  {
236  if ( !v.tableNamesDeclared.contains( pair.first ) )
237  {
238  if ( !errorMsgOut.isEmpty() )
239  errorMsgOut += " ";
240  errorMsgOut += QString( tr( "Table %1 is referenced by column %2, but not selected in FROM / JOIN." ) ).arg( pair.first ).arg( pair.second );
241  }
242  }
243 
244  return errorMsgOut.isEmpty();
245 }
246 
248 // nodes
249 
251 {
252  NodeList* nl = new NodeList;
253  Q_FOREACH ( Node* node, mList )
254  {
255  nl->mList.append( node->clone() );
256  }
257 
258  return nl;
259 }
260 
262 {
263  QString msg;
264  bool first = true;
265  Q_FOREACH ( Node* n, mList )
266  {
267  if ( !first ) msg += ", ";
268  else first = false;
269  msg += n->dump();
270  }
271  return msg;
272 }
273 
274 
275 //
276 
278 {
279  return QString( "%1 %2" ).arg( UnaryOperatorText[mOp], mOperand->dump() );
280 }
281 
283 {
284  return new NodeUnaryOperator( mOp, mOperand->clone() );
285 }
286 
287 //
288 
290 {
291  // see left/right in qgsexpressionparser.yy
292  switch ( mOp )
293  {
294  case boOr:
295  return 1;
296 
297  case boAnd:
298  return 2;
299 
300  case boEQ:
301  case boNE:
302  case boLE:
303  case boGE:
304  case boLT:
305  case boGT:
306  case boLike:
307  case boILike:
308  case boNotLike:
309  case boNotILike:
310  case boIs:
311  case boIsNot:
312  return 3;
313 
314  case boPlus:
315  case boMinus:
316  return 4;
317 
318  case boMul:
319  case boDiv:
320  case boIntDiv:
321  case boMod:
322  return 5;
323 
324  case boPow:
325  return 6;
326 
327  case boConcat:
328  return 7;
329  }
330  Q_ASSERT( 0 && "unexpected binary operator" );
331  return -1;
332 }
333 
335 {
336  // see left/right in qgsexpressionparser.yy
337  switch ( mOp )
338  {
339  case boOr:
340  case boAnd:
341  case boEQ:
342  case boNE:
343  case boLE:
344  case boGE:
345  case boLT:
346  case boGT:
347  case boLike:
348  case boILike:
349  case boNotLike:
350  case boNotILike:
351  case boIs:
352  case boIsNot:
353  case boPlus:
354  case boMinus:
355  case boMul:
356  case boDiv:
357  case boIntDiv:
358  case boMod:
359  case boConcat:
360  return true;
361 
362  case boPow:
363  return false;
364  }
365  Q_ASSERT( 0 && "unexpected binary operator" );
366  return false;
367 }
368 
370 {
374 
375  QString rdump( mOpRight->dump() );
376 
377  // avoid dumping "IS (NOT ...)" as "IS NOT ..."
378  if ( mOp == boIs && ruOp && ruOp->op() == uoNot )
379  {
380  rdump.prepend( '(' ).append( ')' );
381  }
382 
383  QString fmt;
384  if ( leftAssociative() )
385  {
386  fmt += lOp && ( lOp->precedence() < precedence() ) ? "(%1)" : "%1";
387  fmt += " %2 ";
388  fmt += rOp && ( rOp->precedence() <= precedence() ) ? "(%3)" : "%3";
389  }
390  else
391  {
392  fmt += lOp && ( lOp->precedence() <= precedence() ) ? "(%1)" : "%1";
393  fmt += " %2 ";
394  fmt += rOp && ( rOp->precedence() < precedence() ) ? "(%3)" : "%3";
395  }
396 
397  return fmt.arg( mOpLeft->dump(), BinaryOperatorText[mOp], rdump );
398 }
399 
401 {
402  return new NodeBinaryOperator( mOp, mOpLeft->clone(), mOpRight->clone() );
403 }
404 
405 //
406 
408 {
409  return QString( "%1 %2IN (%3)" ).arg( mNode->dump(), mNotIn ? "NOT " : "", mList->dump() );
410 }
411 
413 {
414  return new NodeInOperator( mNode->clone(), mList->clone(), mNotIn );
415 }
416 
417 //
418 
420 {
421  return QString( "%1 %2BETWEEN %3 AND %4" ).arg( mNode->dump(), mNotBetween ? "NOT " : "", mMinVal->dump(), mMaxVal->dump() );
422 }
423 
425 {
426  return new NodeBetweenOperator( mNode->clone(), mMinVal->clone(), mMaxVal->clone(), mNotBetween );
427 }
428 
429 //
430 
432 {
433  return QString( "%1(%2)" ).arg( mName, mArgs ? mArgs->dump() : QString() ); // function
434 }
435 
437 {
438  return new NodeFunction( mName, mArgs ? mArgs->clone() : nullptr );
439 }
440 
441 //
442 
444 {
445  if ( mValue.isNull() )
446  return "NULL";
447 
448  switch ( mValue.type() )
449  {
450  case QVariant::Int:
451  return QString::number( mValue.toInt() );
452  case QVariant::LongLong:
453  return QString::number( mValue.toLongLong() );
454  case QVariant::Double:
455  return QString::number( mValue.toDouble() );
456  case QVariant::String:
457  return quotedString( mValue.toString() );
458  case QVariant::Bool:
459  return mValue.toBool() ? "TRUE" : "FALSE";
460  default:
461  return tr( "[unsupported type;%1; value:%2]" ).arg( mValue.typeName(), mValue.toString() );
462  }
463 }
464 
466 {
467  return new NodeLiteral( mValue );
468 }
469 
470 //
471 
473 {
474  QString ret;
475  if ( mDistinct )
476  ret += "DISTINCT ";
477  if ( !mTableName.isEmpty() )
478  {
479  ret += quotedIdentifierIfNeeded( mTableName );
480  ret += '.';
481  }
482  ret += ( mStar ) ? mName : quotedIdentifierIfNeeded( mName );
483  return ret;
484 }
485 
487 {
488  return cloneThis();
489 }
490 
492 {
493  NodeColumnRef* newColumnRef = new NodeColumnRef( mTableName, mName, mStar );
494  newColumnRef->setDistinct( mDistinct );
495  return newColumnRef;
496 }
497 
498 //
499 
501 {
502  QString ret;
503  ret += mColumnNode->dump();
504  if ( !mAlias.isEmpty() )
505  {
506  ret += " AS ";
507  ret += quotedIdentifierIfNeeded( mAlias );
508  }
509  return ret;
510 }
511 
513 {
514  NodeSelectedColumn* newObj = new NodeSelectedColumn( mColumnNode->clone() );
515  newObj->setAlias( mAlias );
516  return newObj;
517 }
518 
520 {
521  return cloneThis();
522 }
523 //
524 
526 {
527  QString ret;
528  ret = quotedIdentifierIfNeeded( mName );
529  if ( !mAlias.isEmpty() )
530  {
531  ret += " AS ";
532  ret += quotedIdentifierIfNeeded( mAlias );
533  }
534  return ret;
535 }
536 
538 {
539  return new NodeTableDef( mName, mAlias );
540 }
541 
543 {
544  return cloneThis();
545 }
546 
547 //
548 
550 {
551  QString ret = "SELECT ";
552  if ( mDistinct )
553  ret += "DISTINCT ";
554  bool bFirstColumn = true;
555  Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, mColumns )
556  {
557  if ( !bFirstColumn )
558  ret += ", ";
559  bFirstColumn = false;
560  ret += column->dump();
561  }
562  ret += " FROM ";
563  bool bFirstTable = true;
564  Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, mTableList )
565  {
566  if ( !bFirstTable )
567  ret += ", ";
568  bFirstTable = false;
569  ret += table->dump();
570  }
571  Q_FOREACH ( QgsSQLStatement::NodeJoin* join, mJoins )
572  {
573  ret += ' ';
574  ret += join->dump();
575  }
576  if ( mWhere != nullptr )
577  {
578  ret += " WHERE ";
579  ret += mWhere->dump();
580  }
581  if ( !mOrderBy.isEmpty() )
582  {
583  ret += " ORDER BY ";
584  bool bFirst = true;
585  Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* orderBy, mOrderBy )
586  {
587  if ( !bFirst )
588  ret += ", ";
589  bFirst = false;
590  ret += orderBy->dump();
591  }
592  }
593  return ret;
594 }
595 
597 {
599  Q_FOREACH ( QgsSQLStatement::NodeSelectedColumn* column, mColumns )
600  {
601  newColumnList.push_back( column->cloneThis() );
602  }
604  Q_FOREACH ( QgsSQLStatement::NodeTableDef* table, mTableList )
605  {
606  newTableList.push_back( table->cloneThis() );
607  }
608  QgsSQLStatement::NodeSelect* newSelect = new NodeSelect( newTableList, newColumnList, mDistinct );
609  Q_FOREACH ( QgsSQLStatement::NodeJoin* join, mJoins )
610  {
611  newSelect->appendJoin( join->cloneThis() );
612  }
613  if ( mWhere != nullptr )
614  {
615  newSelect->setWhere( mWhere->clone() );
616  }
618  Q_FOREACH ( QgsSQLStatement::NodeColumnSorted* columnSorted, mOrderBy )
619  {
620  newOrderByList.push_back( columnSorted->cloneThis() );
621  }
622  newSelect->setOrderBy( newOrderByList );
623  return newSelect;
624 }
625 
626 //
627 
629 {
630  QString ret;
631  if ( mType != jtDefault )
632  {
633  ret += JoinTypeText[mType];
634  ret += " ";
635  }
636  ret += "JOIN ";
637  ret += mTableDef->dump();
638  if ( mOnExpr != nullptr )
639  {
640  ret += " ON ";
641  ret += mOnExpr->dump();
642  }
643  else
644  {
645  ret += " USING (";
646  bool first = true;
647  Q_FOREACH ( QString column, mUsingColumns )
648  {
649  if ( !first )
650  ret += ", ";
651  first = false;
652  ret += quotedIdentifierIfNeeded( column );
653  }
654  ret += ")";
655  }
656  return ret;
657 }
658 
660 {
661  return cloneThis();
662 }
663 
665 {
666  if ( mOnExpr != nullptr )
667  return new NodeJoin( mTableDef->cloneThis(), mOnExpr->clone(), mType );
668  else
669  return new NodeJoin( mTableDef->cloneThis(), mUsingColumns, mType );
670 }
671 
672 //
673 
675 {
676  QString ret;
677  ret = mColumn->dump();
678  if ( !mAsc )
679  ret += " DESC";
680  return ret;
681 }
682 
684 {
685  return cloneThis();
686 }
687 
689 {
690  return new NodeColumnSorted( mColumn->cloneThis(), mAsc );
691 }
692 
693 //
694 
696 {
697  QString ret( "CAST(" );
698  ret += mNode->dump();
699  ret += " AS ";
700  ret += mType;
701  ret += ')';
702  return ret;
703 }
704 
706 {
707  return new NodeCast( mNode->clone(), mType );
708 }
virtual Node * clone() const override
Generate a clone of this node.
NodeTableDef * cloneThis() const
Clone with same type return.
QString & append(QChar ch)
bool leftAssociative() const
Is left associative ?
Function with a name and arguments node.
void push_back(const T &value)
Node * onExpr() const
On expression.
static const char * BinaryOperatorText[]
virtual Node * clone() const override
Generate a clone of this node.
NodeList * clone() const
Creates a deep copy of this list.
QString & prepend(QChar ch)
virtual QString dump() const override
Abstract virtual dump method.
NodeSelectedColumn * cloneThis() const
Clone with same type return.
virtual void accept(Visitor &v) const override
Support the visitor pattern.
void visit(const QgsSQLStatement::NodeColumnRef &n) override
Visit NodeColumnRef.
void appendJoin(NodeJoin *join)
Append a join.
virtual QString dump() const override
Abstract virtual dump method.
NodeTableDef * tableDef() const
Table definition.
QString statement() const
Return the original, unmodified statement string.
Binary logical/arithmetical operator (AND, OR, =, +, ...)
static QString quotedString(QString text)
Returns a quoted version of a string (in single quotes)
virtual QString dump() const override
Abstract virtual dump method.
bool hasParserError() const
Returns true if an error occurred when parsing the input statement.
bool isNull() const
Abstract node class.
QgsSQLStatement::Node * mRootNode
void clear()
virtual Node * clone() const override
Generate a clone of this node.
virtual Node * clone() const override
Generate a clone of this node.
A visitor that recursively explores all children.
virtual Node * clone() const override
Generate a clone of this node.
static const char * UnaryOperatorText[]
Class for parsing SQL statements.
QString number(int n, int base)
virtual QString dump() const override
Abstract virtual dump method.
virtual QString dump() const
Dump list.
QPair< QString, QString > TableColumnPair
QString name() const
Table name.
void acceptVisitor(Visitor &v) const
Entry function for the visitor pattern.
QString alias() const
Table alias.
virtual QString dump() const =0
Abstract virtual dump method.
virtual QString dump() const override
Abstract virtual dump method.
bool isEmpty() const
virtual Node * clone() const override
Generate a clone of this node.
Literal value (integer, integer64, double, string)
Node * where() const
Return the where clause.
QString name() const
The name of the column.
virtual QString dump() const override
Abstract virtual dump method.
virtual Node * clone() const override
Generate a clone of this node.
void setAlias(const QString &alias)
Set alias name.
Unary logicial/arithmetical operator ( NOT, - )
const Node * rootNode() const
Returns root node of the statement. Root node is null is parsing has failed.
virtual Node * clone() const =0
Generate a clone of this node.
virtual void accept(Visitor &v) const =0
Support the visitor pattern.
NodeJoin * cloneThis() const
Clone with same type return.
virtual QString dump() const override
Abstract virtual dump method.
virtual QString dump() const override
Abstract virtual dump method.
virtual Node * clone() const override
Generate a clone of this node.
QSet< TableColumnPair > tableNamesReferenced
Reference to a column.
QString parserErrorString() const
Returns parser error.
&#39;X BETWEEN y and z&#39; operator
virtual void accept(Visitor &v) const override
Support the visitor pattern.
NodeColumnRef * cloneThis() const
Clone with same type return.
virtual Node * clone() const override
Generate a clone of this node.
virtual Node * clone() const override
Generate a clone of this node.
void setOrderBy(QList< NodeColumnSorted *> orderBy)
Set order by columns.
bool contains(const T &value) const
QList< NodeColumnSorted * > orderBy() const
Return the list of order by columns.
static const char * JoinTypeText[]
QString & replace(int position, int n, QChar after)
static QString quotedIdentifierIfNeeded(QString name)
Returns a quoted column reference (in double quotes) if needed, or otherwise the original string...
QString dump() const
Return statement string, constructed from the internal abstract syntax tree.
QString mid(int position, int n) const
bool doBasicValidationChecks(QString &errorMsgOut) const
Performs basic validity checks.
QgsSQLStatement & operator=(const QgsSQLStatement &other)
Create a copy of this statement.
static QString stripQuotedIdentifier(QString text)
Remove double quotes from an identifier.
NodeColumnSorted * cloneThis() const
Clone with same type return.
QList< NodeTableDef * > tables() const
Return the list of tables.
virtual QString dump() const override
Abstract virtual dump method.
void setDistinct(bool distinct=true)
Set whether this is prefixed by DISTINCT.
virtual QString dump() const override
Abstract virtual dump method.
virtual Node * clone() const override
Generate a clone of this node.
static const QRegExp identifierRE("^[A-Za-z_\0-\f][A-Za-z0-9_\0-\f]*$")
virtual Node * clone() const override
Generate a clone of this node.
int length() const
void visit(const QgsSQLStatement::NodeUnaryOperator &n) override
Visit NodeUnaryOperator.
virtual void accept(Visitor &v) const override
Support the visitor pattern.
void setWhere(Node *where)
Set where clause.
Support for visitor pattern - algorithms dealing with the statement may be implemented without modify...
QString tableName() const
The name of the table.
virtual QString dump() const override
Abstract virtual dump method.
virtual void accept(Visitor &v) const override
Support the visitor pattern.
virtual Node * clone() const override
Generate a clone of this node.
UnaryOperator op() const
Operator.
virtual QString dump() const override
Abstract virtual dump method.
virtual QString dump() const override
Abstract virtual dump method.
static QString quotedIdentifier(QString name)
Returns a quoted column reference (in double quotes)
QgsSQLStatement::Node * parse(const QString &str, QString &parserErrorMsg)
&#39;x IN (y, z)&#39; operator
int compare(const QString &other) const
bool exactMatch(const QString &str) const
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QList< NodeSelectedColumn * > columns() const
Return the list of columns.
QgsSQLStatement(const QString &statement)
Creates a new statement based on the provided string.
QString mParserErrorString
QList< NodeJoin * > joins() const
Return the list of joins.