File indexing completed on 2024-04-14 04:35:16
0001 /* This file is part of the KDE project 0002 Copyright (C) 2012-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 "SqlParserTest.h" 0021 #include <QtTest> 0022 0023 #include <KDbConnectionData> 0024 #include <KDbDriverManager> 0025 #include <KDbNativeStatementBuilder> 0026 #include <KDbQuerySchema> 0027 #include <KDbToken> 0028 0029 Q_DECLARE_METATYPE(KDbEscapedString) 0030 0031 QTEST_GUILESS_MAIN(SqlParserTest) 0032 0033 void SqlParserTest::initTestCase() 0034 { 0035 QString dir(QFile::decodeName(OUTPUT_DIR)); 0036 QString fname("errors.txt"); 0037 m_errorFile.setFileName(dir + QDir::separator() + fname); 0038 QVERIFY2(m_errorFile.open(QFile::WriteOnly | QFile::Text), 0039 qPrintable(QString("Cannot open %1 file").arg(m_errorFile.fileName()))); 0040 m_errorStream.setDevice(&m_errorFile); 0041 } 0042 0043 bool SqlParserTest::openDatabase(const QString &path) 0044 { 0045 KDbConnectionOptions options; 0046 options.setReadOnly(true); 0047 if (!m_utils.testConnectAndUse(path, options)) { 0048 return false; 0049 } 0050 m_parser.reset(new KDbParser(m_utils.connection())); 0051 #if 0 0052 if (m_conn->databaseExists(dbName)) { 0053 if (!m_conn->dropDatabase(dbName)) { 0054 m_conn->disconnect(); 0055 return false; 0056 } 0057 qDebug() << "Database" << dbName << "dropped."; 0058 } 0059 if (!m_conn->createDatabase(dbName)) { 0060 qDebug() << m_conn->result(); 0061 m_conn->disconnect(); 0062 return false; 0063 } 0064 #endif 0065 return true; 0066 } 0067 0068 static void eatComment(QString* string) 0069 { 0070 if (!string->startsWith("--")) { 0071 return; 0072 } 0073 int i = 0; 0074 for (; i < string->length() && string->at(i) == '-'; ++i) 0075 ; 0076 QString result = string->mid(i).trimmed(); 0077 *string = result; 0078 } 0079 0080 static void eatEndLines(QString* string) 0081 { 0082 if (!string->endsWith("--")) { 0083 return; 0084 } 0085 int i = string->length() - 1; 0086 for (; i >= 0 && string->at(i) == '-'; --i) 0087 ; 0088 *string = string->left(i+1).trimmed(); 0089 } 0090 0091 static void eatEndComment(QString* string) 0092 { 0093 int pos = string->indexOf("; --"); 0094 if (pos == -1) { 0095 return; 0096 } 0097 string->truncate(pos); 0098 *string = string->trimmed() + ';'; 0099 } 0100 0101 void SqlParserTest::testParse_data() 0102 { 0103 QTest::addColumn<QString>("fname"); 0104 QTest::addColumn<int>("lineNum"); 0105 QTest::addColumn<KDbEscapedString>("sql"); 0106 QTest::addColumn<bool>("expectError"); 0107 0108 QString dir(QFile::decodeName(FILES_DATA_DIR)); 0109 QString fname("statements.txt"); 0110 QFile input(dir + QDir::separator() + fname); 0111 bool ok = input.open(QFile::ReadOnly | QFile::Text); 0112 QVERIFY2(ok, qPrintable(QString("Could not open data file %1").arg(input.fileName()))); 0113 QTextStream in(&input); 0114 QString category; 0115 QString testName; 0116 bool expectError = false; 0117 int lineNum = 1; 0118 QString dbPath; 0119 bool clearTestName = false; 0120 0121 for (; !in.atEnd(); ++lineNum) { 0122 QString line(in.readLine()); 0123 if (line.startsWith("--")) { // comment 0124 eatComment(&line); 0125 eatEndLines(&line); 0126 if (line.startsWith("TODO:")) { 0127 continue; 0128 } 0129 else if (line.startsWith("CATEGORY: ")) { 0130 if (clearTestName) { 0131 expectError = false; 0132 clearTestName = false; 0133 testName.clear(); 0134 } 0135 category = line.mid(QString("CATEGORY: ").length()).trimmed(); 0136 //qDebug() << "CATEGORY:" << category; 0137 } 0138 else if (line == "QUIT") { 0139 break; 0140 } 0141 else if (line.startsWith("SQLITEFILE: ")) { 0142 if (clearTestName) { 0143 expectError = false; 0144 clearTestName = false; 0145 testName.clear(); 0146 } 0147 ok = dbPath.isEmpty(); 0148 QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was already specified (%2)") 0149 .arg(lineNum).arg(dbPath))); 0150 dbPath = line.mid(QString("SQLITEFILE: ").length()).trimmed(); 0151 dbPath = dir + QDir::separator() + dbPath; 0152 ok = openDatabase(dbPath); 0153 QVERIFY2(ok, qPrintable(QString("Error at line %1: Could not open SQLite file %2") 0154 .arg(lineNum).arg(dbPath))); 0155 } 0156 else if (line.startsWith("ERROR: ")) { 0157 if (clearTestName) { 0158 clearTestName = false; 0159 testName.clear(); 0160 } 0161 expectError = true; 0162 testName = line.mid(QString("ERROR: ").length()).trimmed(); 0163 } 0164 else { 0165 if (clearTestName) { 0166 expectError = false; 0167 clearTestName = false; 0168 testName.clear(); 0169 } 0170 if (!testName.isEmpty()) { 0171 testName.append(" "); 0172 } 0173 testName.append(line); 0174 } 0175 } 0176 else { 0177 eatEndComment(&line); 0178 KDbEscapedString sql(line.trimmed()); 0179 clearTestName = true; 0180 if (sql.isEmpty()) { 0181 expectError = false; 0182 continue; 0183 } 0184 ok = !dbPath.isEmpty(); 0185 QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was not specified, " 0186 "could not execute statement").arg(lineNum))); 0187 0188 QTest::newRow(qPrintable(QString("file %1:%2, category '%3', test '%4', sql '%5'%6") 0189 .arg(fname).arg(lineNum).arg(category).arg(testName).arg(sql.toString()) 0190 .arg(expectError ? ", error expected" : ""))) 0191 << fname << lineNum << sql << expectError; 0192 } 0193 } 0194 input.close(); 0195 } 0196 0197 void SqlParserTest::testParse() 0198 { 0199 QFETCH(QString, fname); 0200 QFETCH(int, lineNum); 0201 QFETCH(KDbEscapedString, sql); 0202 QFETCH(bool, expectError); 0203 0204 QString message; 0205 if (!sql.endsWith(';')) { 0206 message = QString("%1:%2: Missing ';' at the end of line").arg(fname).arg(lineNum); 0207 m_errorStream << fname << ':' << lineNum << ' ' << message << endl; 0208 QVERIFY2(sql.endsWith(';'), qPrintable(message)); 0209 } 0210 sql.chop(1); 0211 //qDebug() << "SQL:" << sql.toString() << expectError; 0212 0213 // 1. Parse 0214 KDbParser *parser = m_parser.data(); 0215 bool ok = parser->parse(sql); 0216 QScopedPointer<KDbQuerySchema> query(parser->query()); 0217 QCOMPARE(parser->query(), nullptr); // second call should always return nullptr 0218 ok = ok && query; 0219 if (ok) { 0220 // sucess, so error cannot be expected 0221 ok = !expectError; 0222 message = "Unexpected success of parsing SQL statement"; 0223 if (!ok) { 0224 m_errorStream << fname << ':' << lineNum << ' ' << message << endl; 0225 if (query) { 0226 const KDbConnectionAndQuerySchema connQuery(parser->connection(), *query); 0227 qDebug() << connQuery; 0228 m_errorStream << KDbUtils::debugString(connQuery) << endl; 0229 } 0230 } 0231 QVERIFY2(ok, qPrintable(message)); 0232 } 0233 else { 0234 // failure, so error should be expected 0235 ok = expectError; 0236 message = QString("%1; Failed to parse SQL Statement:\n\"%2\"\n %3^\n") 0237 .arg(KDbUtils::debugString(parser->error()), 0238 sql.toString(), 0239 QString(parser->error().position() - 1, QChar(' '))); 0240 if (ok) { 0241 qDebug() << parser->error(); 0242 } else { 0243 m_errorStream << fname << ':' << lineNum << message << endl; 0244 } 0245 QVERIFY2(ok, qPrintable(message)); 0246 } 0247 0248 //! @todo support more drivers 0249 if (query) { 0250 // 2. Build native SQL for SQLite 0251 QList<QVariant> params; 0252 KDbEscapedString querySql; 0253 ok = m_utils.driverBuilder()->generateSelectStatement(&querySql, query.data(), params); 0254 QVERIFY2(ok, "Failed to generate native SQLite SQL statement from query"); 0255 //! @todo compare with template 0256 } 0257 0258 if (query) { 0259 // 3. Build KDbSQL 0260 QList<QVariant> params; 0261 KDbEscapedString querySql; 0262 ok = m_utils.kdbBuilder()->generateSelectStatement(&querySql, query.data(), params); 0263 QVERIFY2(ok, "Failed to generate KDbSQL statement from query"); 0264 //! @todo compare with template 0265 0266 // 3.1. Parse the generated KDbSQL again 0267 ok = parser->parse(querySql); 0268 QScopedPointer<KDbQuerySchema> secondQuery(parser->query()); 0269 QCOMPARE(parser->query(), nullptr); // second call should always return nullptr 0270 ok = ok && secondQuery; 0271 QVERIFY2(ok, "Failed to parse generated KDbSQL statement again"); 0272 0273 // 3.2. Compare the original query from step #1 with this query 0274 ok = *query == *secondQuery; 0275 QVERIFY2(ok, "Original query differs from repeatedly parsed query"); 0276 } 0277 } 0278 0279 void SqlParserTest::testTokens() 0280 { 0281 QCOMPARE(KDbToken::SQL_TYPE.value(), 258); 0282 QCOMPARE(KDbToken::AS.value(), 259); 0283 QCOMPARE(KDbToken::AS_EMPTY.value(), 260); 0284 QCOMPARE(KDbToken::ASC.value(), 261); 0285 QCOMPARE(KDbToken::AUTO_INCREMENT.value(), 262); 0286 QCOMPARE(KDbToken::BIT.value(), 263); 0287 QCOMPARE(KDbToken::BITWISE_SHIFT_LEFT.value(), 264); 0288 QCOMPARE(KDbToken::BITWISE_SHIFT_RIGHT.value(), 265); 0289 QCOMPARE(KDbToken::BY.value(), 266); 0290 QCOMPARE(KDbToken::CHARACTER_STRING_LITERAL.value(), 267); 0291 QCOMPARE(KDbToken::CONCATENATION.value(), 268); 0292 QCOMPARE(KDbToken::CREATE.value(), 269); 0293 QCOMPARE(KDbToken::DESC.value(), 270); 0294 QCOMPARE(KDbToken::DISTINCT.value(), 271); 0295 QCOMPARE(KDbToken::DOUBLE_QUOTED_STRING.value(), 272); 0296 QCOMPARE(KDbToken::FROM.value(), 273); 0297 QCOMPARE(KDbToken::JOIN.value(), 274); 0298 QCOMPARE(KDbToken::KEY.value(), 275); 0299 QCOMPARE(KDbToken::LEFT.value(), 276); 0300 QCOMPARE(KDbToken::LESS_OR_EQUAL.value(), 277); 0301 QCOMPARE(KDbToken::GREATER_OR_EQUAL.value(), 278); 0302 QCOMPARE(KDbToken::SQL_NULL.value(), 279); 0303 QCOMPARE(KDbToken::SQL_IS.value(), 280); 0304 QCOMPARE(KDbToken::SQL_IS_NULL.value(), 281); 0305 QCOMPARE(KDbToken::SQL_IS_NOT_NULL.value(), 282); 0306 QCOMPARE(KDbToken::ORDER.value(), 283); 0307 QCOMPARE(KDbToken::PRIMARY.value(), 284); 0308 QCOMPARE(KDbToken::SELECT.value(), 285); 0309 QCOMPARE(KDbToken::INTEGER_CONST.value(), 286); 0310 QCOMPARE(KDbToken::REAL_CONST.value(), 287); 0311 QCOMPARE(KDbToken::RIGHT.value(), 288); 0312 QCOMPARE(KDbToken::SQL_ON.value(), 289); 0313 QCOMPARE(KDbToken::DATE_CONST.value(), 290); 0314 QCOMPARE(KDbToken::DATETIME_CONST.value(), 291); 0315 QCOMPARE(KDbToken::TIME_CONST.value(), 292); 0316 QCOMPARE(KDbToken::TABLE.value(), 293); 0317 QCOMPARE(KDbToken::IDENTIFIER.value(), 294); 0318 QCOMPARE(KDbToken::IDENTIFIER_DOT_ASTERISK.value(), 295); 0319 QCOMPARE(KDbToken::QUERY_PARAMETER.value(), 296); 0320 QCOMPARE(KDbToken::VARCHAR.value(), 297); 0321 QCOMPARE(KDbToken::WHERE.value(), 298); 0322 QCOMPARE(KDbToken::SQL.value(), 299); 0323 QCOMPARE(KDbToken::SQL_TRUE.value(), 300); 0324 QCOMPARE(KDbToken::SQL_FALSE.value(), 301); 0325 QCOMPARE(KDbToken::UNION.value(), 302); 0326 QCOMPARE(KDbToken::SCAN_ERROR.value(), 303); 0327 QCOMPARE(KDbToken::AND.value(), 304); 0328 QCOMPARE(KDbToken::BETWEEN.value(), 305); 0329 QCOMPARE(KDbToken::NOT_BETWEEN.value(), 306); 0330 QCOMPARE(KDbToken::EXCEPT.value(), 307); 0331 QCOMPARE(KDbToken::SQL_IN.value(), 308); 0332 QCOMPARE(KDbToken::INTERSECT.value(), 309); 0333 QCOMPARE(KDbToken::LIKE.value(), 310); 0334 QCOMPARE(KDbToken::ILIKE.value(), 311); 0335 QCOMPARE(KDbToken::NOT_LIKE.value(), 312); 0336 QCOMPARE(KDbToken::NOT.value(), 313); 0337 QCOMPARE(KDbToken::NOT_EQUAL.value(), 314); 0338 QCOMPARE(KDbToken::NOT_EQUAL2.value(), 315); 0339 QCOMPARE(KDbToken::OR.value(), 316); 0340 QCOMPARE(KDbToken::SIMILAR_TO.value(), 317); 0341 QCOMPARE(KDbToken::NOT_SIMILAR_TO.value(), 318); 0342 QCOMPARE(KDbToken::XOR.value(), 319); 0343 QCOMPARE(KDbToken::UMINUS.value(), 320); 0344 0345 //! @todo add extra tokens: BETWEEN_AND, NOT_BETWEEN_AND 0346 } 0347 0348 void SqlParserTest::cleanupTestCase() 0349 { 0350 QVERIFY(m_utils.testDisconnect()); 0351 m_errorFile.close(); 0352 #if 0 0353 if (!m_conn->dropDatabase()) { 0354 qDebug() << m_conn->result(); 0355 } 0356 qDebug() << "Database" << m_conn->data().databaseName() << "dropped."; 0357 #endif 0358 }