#include "parser/parser.h" #include "parser/ast/sqliteselect.h" #include "parser/ast/sqlitecreatetable.h" #include "parser/ast/sqliteinsert.h" #include "parser/ast/sqlitewith.h" #include "parser/ast/sqliteupdate.h" #include "parser/keywords.h" #include "parser/lexer.h" #include "parser/parsererror.h" #include "common/utils_sql.h" #include #include class ParserTest : public QObject { Q_OBJECT public: ParserTest(); private: Parser* parser2 = nullptr; Parser* parser3 = nullptr; private Q_SLOTS: void test(); void testScientificNumber(); void testUniqConflict(); void testGetTableTokens(); void testGetTableTokens2(); void testGetDatabaseTokens(); void testGetFullObjects(); void testGetFullObjects2(); void testUnfinishedSingleSourceWithTolerance(); void testCommentEnding1(); void testCommentEnding2(); void testOper1(); void testBig1(); void testTableFk(); void testDoubleQuotes(); void testInsertError(); void testExpr(); void testCommentBeginMultiline(); void testBetween(); void testBigNum(); void testSelectWith(); void testInsertWithDoubleQuoteValues(); void testParseAndRebuildAlias(); void testRebuildTokensUpdate(); void testRebuildTokensInsertUpsert(); void testGetColumnTokensFromInsertUpsert(); void initTestCase(); void cleanupTestCase(); }; ParserTest::ParserTest() { } void ParserTest::test() { QString sql = "CREATE TRIGGER param_insert_chk_enum " "BEFORE INSERT " "ON param " "WHEN new.type = 'enum' AND " "new.defval IS NOT NULL AND " "new.defval != '' " "BEGIN " "SELECT RAISE(FAIL, 'param_insert_chk_enum failed') " "WHERE NOT EXISTS ( " "SELECT val " "FROM valset " "WHERE param_id = new.param_id AND " "val = new.defval " "); " "END;"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; TokenList tokens = query->getContextTableTokens(); } void ParserTest::testScientificNumber() { QString sql = "SELECT 1e100;"; TokenList tokens = Lexer::tokenize(sql, Dialect::Sqlite3); QVERIFY(tokens.size() == 4); QVERIFY(tokens[2]->type == Token::Type::FLOAT); } void ParserTest::testGetTableTokens() { QString sql = "select someTable.* FROM someTable;"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; TokenList tokens = query->getContextTableTokens(); QVERIFY(tokens.size() == 2); QVERIFY(tokens[0]->type == Token::OTHER); QVERIFY(tokens[1]->type == Token::OTHER); } void ParserTest::testGetTableTokens2() { QString sql = "select db.tab.col FROM someTable;"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; TokenList tokens = query->getContextTableTokens(); QVERIFY(tokens.size() == 2); QVERIFY(tokens[0]->type == Token::OTHER); QVERIFY(tokens[1]->type == Token::OTHER); } void ParserTest::testGetDatabaseTokens() { QString sql = "select * FROM someDb.[table];"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; TokenList tokens = query->getContextTableTokens(); QVERIFY(tokens.size() == 1); QVERIFY(tokens[0]->type == Token::OTHER); } void ParserTest::testGetFullObjects() { QString sql = "select col FROM someDb.[table];"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; QList fullObjects = query->getContextFullObjects(); QVERIFY(fullObjects.size() == 2); for (const SqliteStatement::FullObject& fullObj : fullObjects) { switch (fullObj.type) { case SqliteStatement::FullObject::TABLE: QVERIFY(fullObj.database && fullObj.database->value == "someDb"); QVERIFY(fullObj.object && fullObj.object->value == "[table]"); break; case SqliteStatement::FullObject::DATABASE: QVERIFY(fullObj.database && fullObj.database->value == "someDb"); break; default: QFAIL("Unexpected FullObject type."); } } } void ParserTest::testGetFullObjects2() { QString sql = "select col, tab2.*, abcDb.abcTab.abcCol FROM someDb.[table];"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; QList fullObjects = query->getContextFullObjects(); QVERIFY(fullObjects.size() == 5); for (const SqliteStatement::FullObject& fullObj : fullObjects) { switch (fullObj.type) { case SqliteStatement::FullObject::TABLE: { QVERIFY(fullObj.object); if (fullObj.database && fullObj.database->value == "someDb") QVERIFY(fullObj.object->value == "[table]"); else if (fullObj.database && fullObj.database->value == "abcDb") QVERIFY(fullObj.object->value == "abcTab"); else if (!fullObj.database) QVERIFY(fullObj.object->value == "tab2"); else QFAIL("Invalid TABLE full object."); break; } case SqliteStatement::FullObject::DATABASE: { if (fullObj.database) QVERIFY(fullObj.database->value == "someDb" || fullObj.database->value == "abcDb"); else QFAIL("Invalid DATABASE full object."); break; } default: QFAIL("Unexpected FullObject type."); } } } void ParserTest::testUnfinishedSingleSourceWithTolerance() { QString sql = "SELECT * FROM test.;"; bool res = parser3->parse(sql, true); QVERIFY(res); } void ParserTest::testCommentEnding1() { QString sql = "select 1 --aaa"; bool res = parser3->parse(sql); QVERIFY(res); } void ParserTest::testCommentEnding2() { QString sql = "select 1 /*aaa"; bool res = parser3->parse(sql); QVERIFY(res); } void ParserTest::testOper1() { QString sql = "SELECT dfgd<=2"; TokenList tokens = Lexer::tokenize(sql, Dialect::Sqlite3); QVERIFY(tokens[2]->value == "dfgd"); QVERIFY(tokens[3]->value == "<="); QVERIFY(tokens[4]->value == "2"); } void ParserTest::testBig1() { QString sql = "select " "''|| " "''|| " "''|| " "''|| " "''|| " "''|| " "''|| " "'Ð�TH;°Ñ禅TH;°Ð»Ñ즅TH;½Ð¸Ðº Ñ㦅TH;¿ÑঅTH;°Ð²Ð»ÐµÐ½Ð¸Ñ怜t;/P1>'|| " "''|| " "''||strftime('%d.%m.%Y',d.demdate)||''|| " "'4642'||substr('000000'||d.id, -6, 6)||''|| " "''|| " "''||ins.fullname||''|| " "''||ins.shortname||''|| " "'0'||ins.regnum||''|| " "''|| " "''||ins.inn||''|| " "''||ins.kpp||''|| " "''||ins.addr||''|| " "''||ins.postaddr||''|| " "''||round(dem_s+dem_n+dem_f+dem_t,2)||''|| " "''||strftime('%d.%m.%Y',d.demdate,'+15 day')||''|| " "'0,00'|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "'0,00'|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "'0,00'|| " "'0,00'|| " "''||round(dem_s+dem_n+dem_f+dem_t,2)||''|| " "''||round(dem_s+dem_n,2)||''|| " "'0,00'|| " "''||round(dem_s,2)||''|| " "''||case when d.dem_s>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_s||')' else '' end || ''|| " "''||round(dem_n,2)||''|| " "''||case when d.dem_n>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_n||')' else '' end||''|| " "''||round(dem_f,2)||''|| " "''||case when d.dem_f>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_f||')' else '' end||''|| " "''||round(dem_t,2)||''|| " "''||case when d.dem_t>0 then '(ÐꦅTH;ᦅTH;ꠧ'||kbk.kbk_t||')' else '' end||''|| " "'0,00'|| " "'0,00 0,00 0,00 0,00'|| " "'Ñ঎tilde;㦅TH;±.;'|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "'0,00'|| " "''|| " "''|| " "''|| " "''||strftime('%d.%m.%Y',d.demdate)||''|| " "'0'|| " "''|| " "'0'|| " "''|| " "''||ins.Specname||''|| " "''|| " "'' " "from demands d " "left join ins on ins.regnum=d.regnum " "left join kbk on kbk.cat=ins.Cat " "limit 1"; bool res = parser3->parse(sql); if (!res) { qWarning() << parser3->getErrorString(); ParserError* error = parser3->getErrors().first(); qDebug() << "Error starts at:" << sql.mid(error->getFrom()); } QVERIFY(res); } void ParserTest::testTableFk() { QString sql = "CREATE TABLE test (id INTEGER, FOREIGN KEY (id) REFERENCES test2 (id2));"; parser3->parse(sql); QVERIFY(parser3->getErrors().size() == 0); SqliteQueryPtr query = parser3->getQueries()[0]; SqliteCreateTablePtr creatrTable = query.dynamicCast(); QVERIFY(creatrTable->constraints.size() == 1); QVERIFY(creatrTable->constraints.first()->indexedColumns.size() == 1); QVERIFY(creatrTable->constraints.first()->indexedColumns.first()->name == "id"); QVERIFY(creatrTable->constraints.first()->type == SqliteCreateTable::Constraint::FOREIGN_KEY); QVERIFY(creatrTable->constraints.first()->foreignKey != nullptr); QVERIFY(creatrTable->constraints.first()->foreignKey->foreignTable == "test2"); QVERIFY(creatrTable->constraints.first()->foreignKey->indexedColumns.size() == 1); QVERIFY(creatrTable->constraints.first()->foreignKey->indexedColumns.first()->name == "id2"); } void ParserTest::testDoubleQuotes() { QString sql = "select \"1\""; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getQueries().size() > 0); SqliteQueryPtr query = parser3->getQueries().first(); QVERIFY(query); SqliteSelectPtr select = query.dynamicCast(); QVERIFY(select); QVERIFY(select->coreSelects.size() > 0); QVERIFY(select->coreSelects.first()->resultColumns.size() > 0); SqliteSelect::Core::ResultColumn* rc = select->coreSelects.first()->resultColumns.first(); SqliteExpr* e = rc->expr; QVERIFY(e); QVERIFY(e->mode == SqliteExpr::Mode::ID); QVERIFY(e->possibleDoubleQuotedString); } void ParserTest::testInsertError() { QString sql = "INSERT INTO test "; bool res = parser3->parse(sql); QVERIFY(!res); QVERIFY(parser3->getErrors().size() == 1); } void ParserTest::testExpr() { QString sql = "CAST (CASE WHEN port REGEXP '^[A-Z]' THEN substr(port, 2) ELSE port END AS INT) AS port"; SqliteExpr* expr = parser3->parseExpr(sql); QVERIFY(expr); } void ParserTest::testCommentBeginMultiline() { QString sql = "/*"; TokenList tokens = Lexer::tokenize(sql, Dialect::Sqlite3); QVERIFY(tokens.size() == 1); QVERIFY(tokens[0]->type == Token::COMMENT); } void ParserTest::testBetween() { QString sql = "SELECT * FROM test WHERE a BETWEEN 1 and 2"; bool res = parser3->parse(sql); QVERIFY(res); } void ParserTest::testBigNum() { QString sql = "SELECT ( col - 73016000000 ) FROM tab"; bool res = parser3->parse(sql); QVERIFY(res); } void ParserTest::testUniqConflict() { QString sql = "CREATE TABLE test (x UNIQUE ON CONFLICT FAIL);"; bool res = parser3->parse(sql); QVERIFY(res); SqliteQueryPtr q = parser3->getQueries().first(); TokenList tokens = q->tokens; QVERIFY(tokens[16]->type == Token::Type::PAR_RIGHT); } void ParserTest::testSelectWith() { QString sql = "WITH m (c1, c2) AS (VALUES (1, 'a'), (2, 'b')) SELECT * FROM m;"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); const SqliteQueryPtr query = parser3->getQueries().first(); query->rebuildTokens(); QString detokenized = query->detokenize().replace(" ", ""); QVERIFY(sql.replace(" ", "") == detokenized); } void ParserTest::testInsertWithDoubleQuoteValues() { QString sql = "REPLACE INTO _Variables (Name, TextValue) VALUES (\"varNowTime\", strftime(\"%Y-%m-%dT%H:%M:%S\", \"now\", \"localtime\"));"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); const SqliteInsertPtr insert = parser3->getQueries().first().dynamicCast(); insert->rebuildTokens(); QString detokenized = insert->detokenize().replace(" ", ""); QVERIFY(sql.replace(" ", "") == detokenized); } void ParserTest::testParseAndRebuildAlias() { QString sql = "SELECT x AS [\"abc\".\"def\"];"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); SqliteQueryPtr query = parser3->getQueries().first(); query->rebuildTokens(); QString newSql = query->detokenize(); QVERIFY(sql == newSql); } void ParserTest::testRebuildTokensUpdate() { QString sql = "UPDATE tab SET col1 = 1, (col2, col3) = 2 WHERE x = 3;"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); SqliteUpdatePtr update = parser3->getQueries().first().dynamicCast(); QVERIFY(update->keyValueMap.size() == 2); QVERIFY(update->keyValueMap[1].first.type() == QVariant::StringList); QStringList set2List = update->keyValueMap[1].first.toStringList(); QVERIFY(set2List[0] == "col2"); QVERIFY(set2List[1] == "col3"); QVERIFY(update->where); QVERIFY(!update->table.isNull()); QVERIFY(update->where); update->rebuildTokens(); QVERIFY(update->tokens.detokenize() == sql); } void ParserTest::testRebuildTokensInsertUpsert() { QString sql = "INSERT INTO tab (a1, a2) VALUES (123, 456) ON CONFLICT (b1, b2, b3) DO UPDATE SET col1 = 1, (col2, col3) = 2 WHERE x = 3;"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); SqliteInsertPtr insert = parser3->getQueries().first().dynamicCast(); QVERIFY(insert->upsert); insert->rebuildTokens(); QVERIFY(insert->tokens.detokenize() == sql); } void ParserTest::testGetColumnTokensFromInsertUpsert() { QString sql = "INSERT INTO tab (a1, a2) VALUES (123, 456) ON CONFLICT (b1, b2, b3) DO UPDATE SET col1 = 1, (col2, col3) = 2 WHERE x = 3;"; bool res = parser3->parse(sql); QVERIFY(res); QVERIFY(parser3->getErrors().isEmpty()); SqliteInsertPtr insert = parser3->getQueries().first().dynamicCast(); QVERIFY(insert->upsert); TokenList tk = insert->getContextColumnTokens(); qSort(tk.begin(), tk.end(), [](const TokenPtr& t1, const TokenPtr& t2) {return t1->start < t2->start;}); QVERIFY(tk.toValueList().join(" ") == "a1 a2 b1 b2 b3 col1 col2 col3 x"); } void ParserTest::initTestCase() { initKeywords(); Lexer::staticInit(); initUtilsSql(); parser2 = new Parser(Dialect::Sqlite2); parser3 = new Parser(Dialect::Sqlite3); } void ParserTest::cleanupTestCase() { delete parser2; delete parser3; } QTEST_APPLESS_MAIN(ParserTest) #include "tst_parsertest.moc"