PageRenderTime 26ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/autotests/parser/SqlParserTest.cpp

https://gitlab.com/LongAiR/KDb
C++ | 365 lines | 307 code | 24 blank | 34 comment | 47 complexity | a3b8de257bdd72c7af8e42fd4ee9ad12 MD5 | raw file
  1. /* This file is part of the KDE project
  2. Copyright (C) 2012-2017 Jarosław Staniek <staniek@kde.org>
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Library General Public
  5. License as published by the Free Software Foundation; either
  6. version 2 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Library General Public License for more details.
  11. You should have received a copy of the GNU Library General Public License
  12. along with this library; see the file COPYING.LIB. If not, write to
  13. the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  14. * Boston, MA 02110-1301, USA.
  15. */
  16. #include "SqlParserTest.h"
  17. #include <QtTest>
  18. #include <KDbConnectionData>
  19. #include <KDbDriverManager>
  20. #include <KDbNativeStatementBuilder>
  21. #include <KDbQuerySchema>
  22. #include <KDbToken>
  23. Q_DECLARE_METATYPE(KDbEscapedString)
  24. QTEST_GUILESS_MAIN(SqlParserTest)
  25. void SqlParserTest::initTestCase()
  26. {
  27. QString dir(QFile::decodeName(OUTPUT_DIR));
  28. QString fname("errors.txt");
  29. m_errorFile.setFileName(dir + QDir::separator() + fname);
  30. QVERIFY2(m_errorFile.open(QFile::WriteOnly | QFile::Text),
  31. qPrintable(QString("Cannot open %1 file").arg(m_errorFile.fileName())));
  32. m_errorStream.setDevice(&m_errorFile);
  33. }
  34. bool SqlParserTest::openDatabase(const QString &path)
  35. {
  36. KDbConnectionOptions options;
  37. options.setReadOnly(true);
  38. if (!m_utils.testConnectAndUse(path, options)) {
  39. return false;
  40. }
  41. m_parser.reset(new KDbParser(m_utils.connection.data()));
  42. #if 0
  43. if (m_conn->databaseExists(dbName)) {
  44. if (!m_conn->dropDatabase(dbName)) {
  45. m_conn->disconnect();
  46. return false;
  47. }
  48. qDebug() << "Database" << dbName << "dropped.";
  49. }
  50. if (!m_conn->createDatabase(dbName)) {
  51. qDebug() << m_conn->result();
  52. m_conn->disconnect();
  53. return false;
  54. }
  55. #endif
  56. return true;
  57. }
  58. static void eatComment(QString* string)
  59. {
  60. if (!string->startsWith("--")) {
  61. return;
  62. }
  63. int i = 0;
  64. for (; i < string->length() && string->at(i) == '-'; ++i)
  65. ;
  66. QString result = string->mid(i).trimmed();
  67. *string = result;
  68. }
  69. static void eatEndLines(QString* string)
  70. {
  71. if (!string->endsWith("--")) {
  72. return;
  73. }
  74. int i = string->length() - 1;
  75. for (; i >= 0 && string->at(i) == '-'; --i)
  76. ;
  77. *string = string->left(i+1).trimmed();
  78. }
  79. static void eatEndComment(QString* string)
  80. {
  81. int pos = string->indexOf("; --");
  82. if (pos == -1) {
  83. return;
  84. }
  85. string->truncate(pos);
  86. *string = string->trimmed() + ';';
  87. }
  88. void SqlParserTest::testParse_data()
  89. {
  90. QTest::addColumn<QString>("fname");
  91. QTest::addColumn<int>("lineNum");
  92. QTest::addColumn<KDbEscapedString>("sql");
  93. QTest::addColumn<bool>("expectError");
  94. QString dir(QFile::decodeName(FILES_DATA_DIR));
  95. QString fname("statements.txt");
  96. QFile input(dir + QDir::separator() + fname);
  97. bool ok = input.open(QFile::ReadOnly | QFile::Text);
  98. QVERIFY2(ok, qPrintable(QString("Could not open data file %1").arg(input.fileName())));
  99. QTextStream in(&input);
  100. QString category;
  101. QString testName;
  102. bool expectError = false;
  103. int lineNum = 1;
  104. QString dbPath;
  105. bool clearTestName = false;
  106. for (; !in.atEnd(); ++lineNum) {
  107. QString line(in.readLine());
  108. if (line.startsWith("--")) { // comment
  109. eatComment(&line);
  110. eatEndLines(&line);
  111. if (line.startsWith("TODO:")) {
  112. continue;
  113. }
  114. else if (line.startsWith("CATEGORY: ")) {
  115. if (clearTestName) {
  116. expectError = false;
  117. clearTestName = false;
  118. testName.clear();
  119. }
  120. category = line.mid(QString("CATEGORY: ").length()).trimmed();
  121. //qDebug() << "CATEGORY:" << category;
  122. }
  123. else if (line == "QUIT") {
  124. break;
  125. }
  126. else if (line.startsWith("SQLITEFILE: ")) {
  127. if (clearTestName) {
  128. expectError = false;
  129. clearTestName = false;
  130. testName.clear();
  131. }
  132. ok = dbPath.isEmpty();
  133. QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was already specified (%2)")
  134. .arg(lineNum).arg(dbPath)));
  135. dbPath = line.mid(QString("SQLITEFILE: ").length()).trimmed();
  136. dbPath = dir + QDir::separator() + dbPath;
  137. ok = openDatabase(dbPath);
  138. QVERIFY2(ok, qPrintable(QString("Error at line %1: Could not open SQLite file %2")
  139. .arg(lineNum).arg(dbPath)));
  140. }
  141. else if (line.startsWith("ERROR: ")) {
  142. if (clearTestName) {
  143. clearTestName = false;
  144. testName.clear();
  145. }
  146. expectError = true;
  147. testName = line.mid(QString("ERROR: ").length()).trimmed();
  148. }
  149. else {
  150. if (clearTestName) {
  151. expectError = false;
  152. clearTestName = false;
  153. testName.clear();
  154. }
  155. if (!testName.isEmpty()) {
  156. testName.append(" ");
  157. }
  158. testName.append(line);
  159. }
  160. }
  161. else {
  162. eatEndComment(&line);
  163. KDbEscapedString sql(line.trimmed());
  164. clearTestName = true;
  165. if (sql.isEmpty()) {
  166. expectError = false;
  167. continue;
  168. }
  169. ok = !dbPath.isEmpty();
  170. QVERIFY2(ok, qPrintable(QString("Error at line %1: SQLite file was not specified, "
  171. "could not execute statement").arg(lineNum)));
  172. QTest::newRow(qPrintable(QString("file %1:%2, category '%3', test '%4', sql '%5'%6")
  173. .arg(fname).arg(lineNum).arg(category).arg(testName).arg(sql.toString())
  174. .arg(expectError ? ", error expected" : "")))
  175. << fname << lineNum << sql << expectError;
  176. }
  177. }
  178. input.close();
  179. }
  180. void SqlParserTest::testParse()
  181. {
  182. QFETCH(QString, fname);
  183. QFETCH(int, lineNum);
  184. QFETCH(KDbEscapedString, sql);
  185. QFETCH(bool, expectError);
  186. QString message;
  187. if (!sql.endsWith(';')) {
  188. message = QString("%1:%2: Missing ';' at the end of line").arg(fname).arg(lineNum);
  189. m_errorStream << fname << ':' << lineNum << ' ' << message << endl;
  190. QVERIFY2(sql.endsWith(';'), qPrintable(message));
  191. }
  192. sql.chop(1);
  193. //qDebug() << "SQL:" << sql.toString() << expectError;
  194. // 1. Parse
  195. KDbParser *parser = m_parser.data();
  196. bool ok = parser->parse(sql);
  197. QScopedPointer<KDbQuerySchema> query(parser->query());
  198. QCOMPARE(parser->query(), nullptr); // second call should always return nullptr
  199. ok = ok && query;
  200. if (ok) {
  201. // sucess, so error cannot be expected
  202. ok = !expectError;
  203. message = "Unexpected success of parsing SQL statement";
  204. if (!ok) {
  205. m_errorStream << fname << ':' << lineNum << ' ' << message << endl;
  206. if (query) {
  207. const KDbConnectionAndQuerySchema connQuery(parser->connection(), *query);
  208. qDebug() << connQuery;
  209. m_errorStream << KDbUtils::debugString(connQuery) << endl;
  210. }
  211. }
  212. QVERIFY2(ok, qPrintable(message));
  213. }
  214. else {
  215. // failure, so error should be expected
  216. ok = expectError;
  217. message = QString("%1; Failed to parse SQL Statement:\n\"%2\"\n %3^\n")
  218. .arg(KDbUtils::debugString(parser->error()),
  219. sql.toString(),
  220. QString(parser->error().position() - 1, QChar(' ')));
  221. if (ok) {
  222. qDebug() << parser->error();
  223. } else {
  224. m_errorStream << fname << ':' << lineNum << message << endl;
  225. }
  226. QVERIFY2(ok, qPrintable(message));
  227. }
  228. //! @todo support more drivers
  229. if (query) {
  230. // 2. Build native SQL for SQLite
  231. QList<QVariant> params;
  232. KDbEscapedString querySql;
  233. ok = m_utils.driverBuilder()->generateSelectStatement(&querySql, query.data(), params);
  234. QVERIFY2(ok, "Failed to generate native SQLite SQL statement from query");
  235. //! @todo compare with template
  236. }
  237. if (query) {
  238. // 3. Build KDbSQL
  239. QList<QVariant> params;
  240. KDbEscapedString querySql;
  241. ok = m_utils.kdbBuilder()->generateSelectStatement(&querySql, query.data(), params);
  242. QVERIFY2(ok, "Failed to generate KDbSQL statement from query");
  243. //! @todo compare with template
  244. // 3.1. Parse the generated KDbSQL again
  245. ok = parser->parse(querySql);
  246. QScopedPointer<KDbQuerySchema> secondQuery(parser->query());
  247. QCOMPARE(parser->query(), nullptr); // second call should always return nullptr
  248. ok = ok && secondQuery;
  249. QVERIFY2(ok, "Failed to parse generated KDbSQL statement again");
  250. // 3.2. Compare the original query from step #1 with this query
  251. ok = *query == *secondQuery;
  252. QVERIFY2(ok, "Original query differs from repeatedly parsed query");
  253. }
  254. }
  255. void SqlParserTest::testTokens()
  256. {
  257. KDbToken t = KDbToken::LEFT;
  258. //qDebug() << t << t.toChar() << t.value() << t.isValid();
  259. t = '+';
  260. //qDebug() << t << t.toChar() << t.value() << t.isValid();
  261. t = KDbToken();
  262. //qDebug() << t << t.toChar() << t.value() << t.isValid();
  263. QCOMPARE(KDbToken::SQL_TYPE.value(), 258);
  264. QCOMPARE(KDbToken::AS.value(), 259);
  265. QCOMPARE(KDbToken::AS_EMPTY.value(), 260);
  266. QCOMPARE(KDbToken::ASC.value(), 261);
  267. QCOMPARE(KDbToken::AUTO_INCREMENT.value(), 262);
  268. QCOMPARE(KDbToken::BIT.value(), 263);
  269. QCOMPARE(KDbToken::BITWISE_SHIFT_LEFT.value(), 264);
  270. QCOMPARE(KDbToken::BITWISE_SHIFT_RIGHT.value(), 265);
  271. QCOMPARE(KDbToken::BY.value(), 266);
  272. QCOMPARE(KDbToken::CHARACTER_STRING_LITERAL.value(), 267);
  273. QCOMPARE(KDbToken::CONCATENATION.value(), 268);
  274. QCOMPARE(KDbToken::CREATE.value(), 269);
  275. QCOMPARE(KDbToken::DESC.value(), 270);
  276. QCOMPARE(KDbToken::DISTINCT.value(), 271);
  277. QCOMPARE(KDbToken::DOUBLE_QUOTED_STRING.value(), 272);
  278. QCOMPARE(KDbToken::FROM.value(), 273);
  279. QCOMPARE(KDbToken::JOIN.value(), 274);
  280. QCOMPARE(KDbToken::KEY.value(), 275);
  281. QCOMPARE(KDbToken::LEFT.value(), 276);
  282. QCOMPARE(KDbToken::LESS_OR_EQUAL.value(), 277);
  283. QCOMPARE(KDbToken::GREATER_OR_EQUAL.value(), 278);
  284. QCOMPARE(KDbToken::SQL_NULL.value(), 279);
  285. QCOMPARE(KDbToken::SQL_IS.value(), 280);
  286. QCOMPARE(KDbToken::SQL_IS_NULL.value(), 281);
  287. QCOMPARE(KDbToken::SQL_IS_NOT_NULL.value(), 282);
  288. QCOMPARE(KDbToken::ORDER.value(), 283);
  289. QCOMPARE(KDbToken::PRIMARY.value(), 284);
  290. QCOMPARE(KDbToken::SELECT.value(), 285);
  291. QCOMPARE(KDbToken::INTEGER_CONST.value(), 286);
  292. QCOMPARE(KDbToken::REAL_CONST.value(), 287);
  293. QCOMPARE(KDbToken::RIGHT.value(), 288);
  294. QCOMPARE(KDbToken::SQL_ON.value(), 289);
  295. QCOMPARE(KDbToken::DATE_CONST.value(), 290);
  296. QCOMPARE(KDbToken::DATETIME_CONST.value(), 291);
  297. QCOMPARE(KDbToken::TIME_CONST.value(), 292);
  298. QCOMPARE(KDbToken::TABLE.value(), 293);
  299. QCOMPARE(KDbToken::IDENTIFIER.value(), 294);
  300. QCOMPARE(KDbToken::IDENTIFIER_DOT_ASTERISK.value(), 295);
  301. QCOMPARE(KDbToken::QUERY_PARAMETER.value(), 296);
  302. QCOMPARE(KDbToken::VARCHAR.value(), 297);
  303. QCOMPARE(KDbToken::WHERE.value(), 298);
  304. QCOMPARE(KDbToken::SQL.value(), 299);
  305. QCOMPARE(KDbToken::SQL_TRUE.value(), 300);
  306. QCOMPARE(KDbToken::SQL_FALSE.value(), 301);
  307. QCOMPARE(KDbToken::UNION.value(), 302);
  308. QCOMPARE(KDbToken::SCAN_ERROR.value(), 303);
  309. QCOMPARE(KDbToken::AND.value(), 304);
  310. QCOMPARE(KDbToken::BETWEEN.value(), 305);
  311. QCOMPARE(KDbToken::NOT_BETWEEN.value(), 306);
  312. QCOMPARE(KDbToken::EXCEPT.value(), 307);
  313. QCOMPARE(KDbToken::SQL_IN.value(), 308);
  314. QCOMPARE(KDbToken::INTERSECT.value(), 309);
  315. QCOMPARE(KDbToken::LIKE.value(), 310);
  316. QCOMPARE(KDbToken::ILIKE.value(), 311);
  317. QCOMPARE(KDbToken::NOT_LIKE.value(), 312);
  318. QCOMPARE(KDbToken::NOT.value(), 313);
  319. QCOMPARE(KDbToken::NOT_EQUAL.value(), 314);
  320. QCOMPARE(KDbToken::NOT_EQUAL2.value(), 315);
  321. QCOMPARE(KDbToken::OR.value(), 316);
  322. QCOMPARE(KDbToken::SIMILAR_TO.value(), 317);
  323. QCOMPARE(KDbToken::NOT_SIMILAR_TO.value(), 318);
  324. QCOMPARE(KDbToken::XOR.value(), 319);
  325. QCOMPARE(KDbToken::UMINUS.value(), 320);
  326. //! @todo add extra tokens: BETWEEN_AND, NOT_BETWEEN_AND
  327. }
  328. void SqlParserTest::cleanupTestCase()
  329. {
  330. QVERIFY(m_utils.testDisconnect());
  331. m_errorFile.close();
  332. #if 0
  333. if (!m_conn->dropDatabase()) {
  334. qDebug() << m_conn->result();
  335. }
  336. qDebug() << "Database" << m_conn->data().databaseName() << "dropped.";
  337. #endif
  338. }