File indexing completed on 2024-12-01 13:00:14

0001 /* This file is part of the KDE project
0002    Copyright (C) 2004-2017 Jarosław Staniek <staniek@kde.org>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017  * Boston, MA 02110-1301, USA.
0018 */
0019 
0020 #include "KDbParser_p.h"
0021 #include "KDb.h"
0022 #include "KDbConnection.h"
0023 #include "KDbTableSchema.h"
0024 #include "KDbQueryAsterisk.h"
0025 #include "KDbQuerySchema.h"
0026 #include "KDbQuerySchema_p.h"
0027 #include "KDbOrderByColumn.h"
0028 #include "kdb_debug.h"
0029 #include "generated/sqlparser.h"
0030 
0031 #include <QMutableListIterator>
0032 
0033 KDbParser *globalParser = nullptr;
0034 KDbField *globalField = nullptr;
0035 QList<KDbField*> fieldList;
0036 int globalCurrentPos = 0;
0037 QByteArray globalToken;
0038 
0039 extern int yylex_destroy(void);
0040 
0041 //-------------------------------------
0042 
0043 KDbParserPrivate::KDbParserPrivate()
0044     : table(nullptr), query(nullptr), connection(nullptr), initialized(false)
0045 {
0046     reset();
0047 }
0048 
0049 KDbParserPrivate::~KDbParserPrivate()
0050 {
0051     reset();
0052 }
0053 
0054 void KDbParserPrivate::reset()
0055 {
0056     statementType = KDbParser::NoType;
0057     sql.clear();
0058     error = KDbParserError();
0059     delete table;
0060     table = nullptr;
0061     delete query;
0062     query = nullptr;
0063 }
0064 
0065 void KDbParserPrivate::setStatementType(KDbParser::StatementType type)
0066 {
0067     statementType = type;
0068 }
0069 
0070 void KDbParserPrivate::setError(const KDbParserError &err)
0071 {
0072     error = err;
0073 }
0074 
0075 void KDbParserPrivate::setTableSchema(KDbTableSchema *table)
0076 {
0077     delete this->table;
0078     this->table = table;
0079 }
0080 
0081 void KDbParserPrivate::setQuerySchema(KDbQuerySchema *query)
0082 {
0083     if (this->query != query) {
0084         delete this->query;
0085     }
0086     this->query = query;
0087 }
0088 
0089 KDbQuerySchema* KDbParserPrivate::createQuery()
0090 {
0091     return query ? query : new KDbQuerySchema;
0092 }
0093 
0094 //-------------------------------------
0095 
0096 KDbParseInfo::KDbParseInfo(KDbQuerySchema *query)
0097  : d(new Private)
0098 {
0099     d->querySchema = query;
0100 }
0101 
0102 KDbParseInfo::~KDbParseInfo()
0103 {
0104     delete d;
0105 }
0106 
0107 QList<int> KDbParseInfo::tablesAndAliasesForName(const QString &tableOrAliasName) const
0108 {
0109     QList<int> result;
0110     const QList<int> *list = d->repeatedTablesAndAliases.value(tableOrAliasName);
0111     if (list) {
0112         result = *list;
0113     }
0114     if (result.isEmpty()) {
0115         int position = d->querySchema->tablePositionForAlias(tableOrAliasName);
0116         if (position == -1) {
0117             position = d->querySchema->tablePosition(tableOrAliasName);
0118             if (position != -1) {
0119                 result.append(position);
0120             }
0121         } else {
0122             result.append(position);
0123         }
0124     }
0125     return result;
0126 }
0127 
0128 KDbQuerySchema* KDbParseInfo::querySchema() const
0129 {
0130     return d->querySchema;
0131 }
0132 
0133 QString KDbParseInfo::errorMessage() const
0134 {
0135     return d->errorMessage;
0136 }
0137 
0138 QString KDbParseInfo::errorDescription() const
0139 {
0140     return d->errorDescription;
0141 }
0142 
0143 void KDbParseInfo::setErrorMessage(const QString &message)
0144 {
0145     d->errorMessage = message;
0146 }
0147 
0148 void KDbParseInfo::setErrorDescription(const QString &description)
0149 {
0150     d->errorDescription = description;
0151 }
0152 
0153 //-------------------------------------
0154 
0155 KDbParseInfoInternal::KDbParseInfoInternal(KDbQuerySchema *query)
0156 : KDbParseInfo(query)
0157 {
0158 }
0159 
0160 KDbParseInfoInternal::~KDbParseInfoInternal()
0161 {
0162 }
0163 
0164 void KDbParseInfoInternal::appendPositionForTableOrAliasName(const QString &tableOrAliasName, int pos)
0165 {
0166     QList<int> *list = d->repeatedTablesAndAliases.value(tableOrAliasName);
0167     if (!list) {
0168         list = new QList<int>();
0169         d->repeatedTablesAndAliases.insert(tableOrAliasName, list);
0170     }
0171     list->append(pos);
0172 }
0173 
0174 //-------------------------------------
0175 
0176 extern int yyparse();
0177 extern void tokenize(const char *data);
0178 
0179 void yyerror(const char *str)
0180 {
0181     kdbDebug() << "error: " << str;
0182     kdbDebug() << "at character " << globalCurrentPos << " near tooken " << globalToken;
0183     KDbParserPrivate::get(globalParser)->setStatementType(KDbParser::NoType);
0184 
0185     const bool otherError = (qstrnicmp(str, "other error", 11) == 0);
0186     const bool syntaxError = qstrnicmp(str, "syntax error", 12) == 0;
0187     if ((globalParser->error().type().isEmpty()
0188          && (str == nullptr || strlen(str) == 0 || syntaxError))
0189         || otherError)
0190     {
0191         kdbDebug() << globalParser->statement();
0192         QString ptrline(globalCurrentPos, QLatin1Char(' '));
0193 
0194         ptrline += QLatin1String("^");
0195 
0196         kdbDebug() << ptrline;
0197 
0198 #if 0
0199         //lexer may add error messages
0200         QString lexerErr = globalParser->error().message();
0201 
0202         QString errtypestr = QLatin1String(str);
0203         if (lexerErr.isEmpty()) {
0204             if (errtypestr.startsWith(QString::fromLatin1("parse error, expecting `IDENTIFIER'"))) {
0205                 lexerErr = KDbParser::tr("identifier was expected");
0206             }
0207         }
0208 #endif
0209 
0210         //! @todo exact invalid expression can be selected in the editor, based on KDbParseInfo data
0211         if (!otherError) {
0212             const bool isKDbSqlKeyword = KDb::isKDbSqlKeyword(globalToken);
0213             if (isKDbSqlKeyword || syntaxError) {
0214                 if (isKDbSqlKeyword) {
0215                     KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"),
0216                                                           KDbParser::tr("\"%1\" is a reserved keyword.").arg(QLatin1String(globalToken)),
0217                                                           globalToken, globalCurrentPos));
0218                 } else {
0219                     KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Syntax Error"),
0220                                                           KDbParser::tr("Syntax error."),
0221                                                           globalToken, globalCurrentPos));
0222                 }
0223             } else {
0224                 KDbParserPrivate::get(globalParser)->setError(KDbParserError(KDbParser::tr("Error"),
0225                                                       KDbParser::tr("Error near \"%1\".").arg(QLatin1String(globalToken)),
0226                                                       globalToken, globalCurrentPos));
0227             }
0228         }
0229     }
0230 }
0231 
0232 void setError(const QString& errName, const QString& errDesc)
0233 {
0234     KDbParserPrivate::get(globalParser)->setError(KDbParserError(errName, errDesc, globalToken, globalCurrentPos));
0235     yyerror(qPrintable(errName));
0236 }
0237 
0238 void setError(const QString& errDesc)
0239 {
0240     setError(KDbParser::tr("Other error"), errDesc);
0241 }
0242 
0243 /* this is better than assert() */
0244 #define IMPL_ERROR(errmsg) setError(KDbParser::tr("Implementation error"), QLatin1String(errmsg))
0245 
0246 //! @internal Parses @a data for parser @a p
0247 //! @todo Make it REENTRANT
0248 bool parseData()
0249 {
0250     fieldList.clear();
0251 
0252     const KDbEscapedString sql(globalParser->statement());
0253     if (sql.isEmpty()) {
0254         KDbParserError err(KDbParser::tr("Error"),
0255                            KDbParser::tr("No query statement specified."),
0256                            globalToken, globalCurrentPos);
0257         KDbParserPrivate::get(globalParser)->setError(err);
0258         yyerror("");
0259         return false;
0260     }
0261 
0262     const char *data = sql.constData();
0263     tokenize(data);
0264     if (!globalParser->error().type().isEmpty()) {
0265         return false;
0266     }
0267 
0268     bool ok = yyparse() == 0;
0269     if (ok && globalCurrentPos < sql.length()) {
0270         kdbDebug() << "Parse error: tokens left"
0271                    << "globalCurrentPos:" << globalCurrentPos << "sql.length():" << sql.length()
0272                    << "globalToken:" << QString::fromUtf8(globalToken);
0273         KDbParserError err(KDbParser::tr("Error"),
0274                            KDbParser::tr("Unexpected character."),
0275                            globalToken, globalCurrentPos);
0276         KDbParserPrivate::get(globalParser)->setError(err);
0277         yyerror("");
0278         ok = false;
0279     }
0280     if (ok && globalParser->statementType() == KDbParser::Select) {
0281         kdbDebug() << "parseData(): ok";
0282 //   kdbDebug() << "parseData(): " << tableDict.count() << " loaded tables";
0283         /*   KDbTableSchema *ts;
0284               for(QDictIterator<KDbTableSchema> it(tableDict); KDbTableSchema *s = tableList.first(); s; s = tableList.next())
0285               {
0286                 kdbDebug() << " " << s->name();
0287               }*/
0288     } else {
0289         ok = false;
0290     }
0291     yylex_destroy();
0292     return ok;
0293 }
0294 
0295 
0296 /*! Adds @a columnExpr to @a parseInfo
0297  The column can be in a form table.field, tableAlias.field or field.
0298  @return true on success. On error message in globalParser object is updated.
0299 */
0300 bool addColumn(KDbParseInfo *parseInfo, const KDbExpression &columnExpr)
0301 {
0302     if (!KDbExpression(columnExpr).validate(parseInfo)) { // (KDbExpression(columnExpr) used to avoid constness problem)
0303         setError(parseInfo->errorMessage(), parseInfo->errorDescription());
0304         return false;
0305     }
0306 
0307     const KDbVariableExpression v_e(columnExpr.toVariable());
0308     if (columnExpr.expressionClass() == KDb::VariableExpression && !v_e.isNull()) {
0309         //it's a variable:
0310         if (v_e.name() == QLatin1String("*")) {//all tables asterisk
0311             if (parseInfo->querySchema()->tables()->isEmpty()) {
0312                 setError(KDbParser::tr("\"*\" could not be used if no tables are specified."));
0313                 return false;
0314             }
0315             KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema());
0316             if (!parseInfo->querySchema()->addAsterisk(a)) {
0317                 delete a;
0318                 setError(KDbParser::tr("\"*\" could not be added."));
0319                 return false;
0320             }
0321         } else if (v_e.tableForQueryAsterisk()) {//one-table asterisk
0322             KDbQueryAsterisk *a = new KDbQueryAsterisk(parseInfo->querySchema(), *v_e.tableForQueryAsterisk());
0323             if (!parseInfo->querySchema()->addAsterisk(a)) {
0324                 delete a;
0325                 setError(KDbParser::tr("\"<table>.*\" could not be added."));
0326                 return false;
0327             }
0328         } else if (v_e.field()) {//"table.field" or "field" (bound to a table or not)
0329             if (!parseInfo->querySchema()->addField(v_e.field(), v_e.tablePositionForField())) {
0330                 setError(KDbParser::tr("Could not add binding to a field."));
0331                 return false;
0332             }
0333         } else {
0334             IMPL_ERROR("addColumn(): unknown case!");
0335             return false;
0336         }
0337         return true;
0338     }
0339 
0340     //it's complex expression
0341     return parseInfo->querySchema()->addExpression(columnExpr);
0342 }
0343 
0344 KDbQuerySchema* buildSelectQuery(
0345     KDbQuerySchema* querySchema, KDbNArgExpression* _colViews,
0346     KDbNArgExpression* _tablesList, SelectOptionsInternal* options)
0347 {
0348     KDbParseInfoInternal parseInfo(querySchema);
0349 
0350     // remove from heap (using heap was requered because parser uses union)
0351     KDbNArgExpression colViews;
0352     if (_colViews) {
0353         colViews = *_colViews;
0354         delete _colViews;
0355     }
0356     KDbNArgExpression tablesList;
0357     if (_tablesList) {
0358         tablesList = *_tablesList;
0359         delete _tablesList;
0360     }
0361     QScopedPointer<SelectOptionsInternal> optionsPtr(options);
0362     QScopedPointer<KDbQuerySchema> querySchemaPtr(querySchema); // destroy query on any error
0363 
0364     //-------tables list
0365     int columnNum = 0;
0366     /*! @todo use this later if there are columns that use database fields,
0367               e.g. "SELECT 1 from table1 t, table2 t") is ok however. */
0368     //used to collect information about first repeated table name or alias:
0369     if (!tablesList.isEmpty()) {
0370         for (int i = 0; i < tablesList.argCount(); i++, columnNum++) {
0371             KDbExpression e(tablesList.arg(i));
0372             KDbVariableExpression t_e;
0373             QString aliasString;
0374             if (e.expressionClass() == KDb::SpecialBinaryExpression) {
0375                 KDbBinaryExpression t_with_alias = e.toBinary();
0376                 Q_ASSERT(e.isBinary());
0377                 Q_ASSERT(t_with_alias.left().expressionClass() == KDb::VariableExpression);
0378                 Q_ASSERT(t_with_alias.right().expressionClass() == KDb::VariableExpression
0379                        && (t_with_alias.token() == KDbToken::AS || t_with_alias.token() == KDbToken::AS_EMPTY));
0380                 t_e = t_with_alias.left().toVariable();
0381                 aliasString = t_with_alias.right().toVariable().name();
0382             } else {
0383                 t_e = e.toVariable();
0384             }
0385             Q_ASSERT(t_e.isVariable());
0386             QString tname = t_e.name();
0387             KDbTableSchema *s = globalParser->connection()->tableSchema(tname);
0388             if (!s) {
0389                 setError(KDbParser::tr("Table \"%1\" does not exist.").arg(tname));
0390                 return nullptr;
0391             }
0392             QString tableOrAliasName = KDb::iifNotEmpty(aliasString, tname);
0393             if (!aliasString.isEmpty()) {
0394 //    kdbDebug() << "- add alias for table: " << aliasString;
0395                 const int tablePosition = querySchema->tablePositionForAlias(aliasString);
0396                 if (tablePosition != -1 && tablePosition != i) {
0397                     KDbTableSchema* tableForAlias = querySchema->tables()->at(tablePosition);
0398                     setError(KDbParser::tr("Could not set alias \"%1\" for table \"%2\". "
0399                                            "This alias is already set for table \"%3\".")
0400                              .arg(aliasString, tname, tableForAlias->name()));
0401                     break;
0402                 }
0403             }
0404             // 1. collect information about first repeated table name or alias
0405             //    (potential ambiguity)
0406             parseInfo.appendPositionForTableOrAliasName(tableOrAliasName, i);
0407 //   kdbDebug() << "addTable: " << tname;
0408             querySchema->addTable(s, aliasString);
0409         }
0410     }
0411 
0412     /* set parent table if there's only one */
0413     if (querySchema->tables()->count() == 1)
0414         querySchema->setMasterTable(querySchema->tables()->first());
0415 
0416     //-------add fields
0417     if (!colViews.isEmpty()) {
0418         columnNum = 0;
0419         bool containsAsteriskColumn = false; // used to check duplicated asterisks (disallowed)
0420         for (int i = 0; i < colViews.argCount(); i++, columnNum++) {
0421             const KDbExpression e(colViews.arg(i));
0422             KDbExpression columnExpr(e);
0423             KDbVariableExpression aliasVariable;
0424             if (e.expressionClass() == KDb::SpecialBinaryExpression && e.isBinary()
0425                     && (e.token() == KDbToken::AS || e.token() == KDbToken::AS_EMPTY)) {
0426                 //KDb::SpecialBinaryExpression: with alias
0427                 columnExpr = e.toBinary().left();
0428                 aliasVariable = e.toBinary().right().toVariable();
0429                 if (aliasVariable.isNull()) {
0430                     setError(KDbParser::tr("Invalid alias definition for column \"%1\".")
0431                                            .arg(columnExpr.toString(nullptr).toString())); //ok?
0432                     break;
0433                 }
0434             }
0435 
0436             const KDb::ExpressionClass c = columnExpr.expressionClass();
0437             const bool isExpressionField =
0438                 c == KDb::ConstExpression
0439                 || c == KDb::UnaryExpression
0440                 || c == KDb::ArithmeticExpression
0441                 || c == KDb::LogicalExpression
0442                 || c == KDb::RelationalExpression
0443                 || c == KDb::FunctionExpression
0444                 || c == KDb::AggregationExpression
0445                 || c == KDb::QueryParameterExpression;
0446 
0447             if (c == KDb::VariableExpression) {
0448                 if (columnExpr.toVariable().name() == QLatin1String("*")) {
0449                     if (containsAsteriskColumn) {
0450                         setError(KDbParser::tr("More than one asterisk \"*\" is not allowed."));
0451                         return nullptr;
0452                     }
0453                     else {
0454                         containsAsteriskColumn = true;
0455                     }
0456                 }
0457                 // addColumn() will handle this
0458             }
0459             else if (isExpressionField) {
0460                 //expression object will be reused, take, will be owned, do not destroy
0461 //  kdbDebug() << colViews->list.count() << " " << it.current()->debugString();
0462 //! @todo IMPORTANT: it.remove();
0463             } else if (aliasVariable.isNull()) {
0464                 setError(KDbParser::tr("Invalid \"%1\" column definition.")
0465                                        .arg(e.toString(nullptr).toString())); //ok?
0466                 break;
0467             }
0468             else {
0469                 //take first (left) argument of the special binary expr, will be owned, do not destroy
0470                 e.toBinary().setLeft(KDbExpression());
0471             }
0472 
0473             if (!addColumn(&parseInfo, columnExpr)) {
0474                 break;
0475             }
0476 
0477             if (!aliasVariable.isNull()) {
0478 //    kdbDebug() << "ALIAS \"" << aliasVariable->name << "\" set for column "
0479 //     << columnNum;
0480                 const int currentColumn = querySchema->columnPositionForAlias(aliasVariable.name());
0481                 if (currentColumn != -1) {
0482                     setError(KDbParser::tr("Could not set alias \"%1\" for column #%2. This alias is already set for column #%3.")
0483                              .arg(aliasVariable.name()).arg(columnNum + 1).arg(currentColumn + 1));
0484                     break;
0485                 }
0486                 if (!querySchema->setColumnAlias(columnNum, aliasVariable.name())) {
0487                     setError(KDbParser::tr("Could not set alias \"%1\" for column #%2.")
0488                              .arg(aliasVariable.name()).arg(columnNum + 1));
0489                     break;
0490                 }
0491             }
0492         } // for
0493         if (!globalParser->error().message().isEmpty()) { // we could not return earlier (inside the loop)
0494                                                           // because we want run CLEANUP what could crash QMutableListIterator.
0495             return nullptr;
0496         }
0497     }
0498     //----- SELECT options
0499     if (options) {
0500         //----- WHERE expr.
0501         if (!options->whereExpr.isNull()) {
0502             if (!options->whereExpr.validate(&parseInfo)) {
0503                 setError(parseInfo.errorMessage(), parseInfo.errorDescription());
0504                 return nullptr;
0505             }
0506             KDbQuerySchemaPrivate::setWhereExpressionInternal(querySchema, options->whereExpr);
0507         }
0508         //----- ORDER BY
0509         if (options->orderByColumns) {
0510             KDbOrderByColumnList *orderByColumnList = querySchema->orderByColumnList();
0511             int count = options->orderByColumns->count();
0512             QList<OrderByColumnInternal>::ConstIterator it(options->orderByColumns->constEnd());
0513             --it;
0514             for (;count > 0; --it, --count)
0515                 /*opposite direction due to parser specifics*/
0516             {
0517                 // first, try to find a column name or alias (outside of asterisks)
0518                 KDbQueryColumnInfo *columnInfo = querySchema->columnInfo(
0519                     globalParser->connection(), (*it).aliasOrName,
0520                     KDbQuerySchema::ExpandMode::Unexpanded /*outside of asterisks*/);
0521                 if (columnInfo) {
0522                     orderByColumnList->appendColumn(columnInfo, (*it).order);
0523                 } else {
0524                     //failed, try to find a field name within all the tables
0525                     if ((*it).columnNumber != -1) {
0526                         if (!orderByColumnList->appendColumn(globalParser->connection(),
0527                                                              querySchema, (*it).order,
0528                                                              (*it).columnNumber - 1))
0529                         {
0530                             setError(KDbParser::tr("Could not define sorting. Column at "
0531                                                    "position %1 does not exist.")
0532                                                    .arg((*it).columnNumber));
0533                             return nullptr;
0534                         }
0535                     } else {
0536                         KDbField * f = querySchema->findTableField((*it).aliasOrName);
0537                         if (!f) {
0538                             setError(KDbParser::tr("Could not define sorting. "
0539                                                    "Column name or alias \"%1\" does not exist.")
0540                                                    .arg((*it).aliasOrName));
0541                             return nullptr;
0542                         }
0543                         orderByColumnList->appendField(f, (*it).order);
0544                     }
0545                 }
0546             }
0547         }
0548     }
0549 // kdbDebug() << "Select ColViews=" << (colViews ? colViews->debugString() : QString())
0550 //  << " Tables=" << (tablesList ? tablesList->debugString() : QString()s);
0551     return querySchemaPtr.take();
0552 }