#include "parser/keywords.h" #include "parser/lexer.h" #include "tablemodifier.h" #include "parser/parser.h" #include "db/db.h" #include "dbsqlite3mock.h" #include "mocks.h" #include #include #include class TableModifierTest : public QObject { Q_OBJECT public: TableModifierTest(); private: void verifyRe(const QString& re, const QString& sql, Qt::CaseSensitivity cs = Qt::CaseSensitive); Db* db = nullptr; static const constexpr char* mainTableDdl = "CREATE TABLE test (id int, val text, val2 text);"; SqliteCreateTablePtr createTable; private Q_SLOTS: void initTestCase(); void init(); void cleanup(); void testCase1(); void testCase2(); void testCase3(); void testCase4(); void testCase5(); void testCase6(); void testCase7(); }; TableModifierTest::TableModifierTest() { } void TableModifierTest::verifyRe(const QString& re, const QString& sql, Qt::CaseSensitivity cs) { QRegExp regExp(re, cs); QVERIFY2(regExp.exactMatch(sql), QString("Failed RegExp validation:\n%1\nfor SQL:\n%2").arg(re).arg(sql).toLatin1().data()); } void TableModifierTest::testCase1() { db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); TableModifier mod(db, "test"); createTable->table = "test2"; mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 1. Create new (with new name) * 2. Copy data to new * 4. Rename referencing table to temp name in 2 steps. * 5. Second step of renaming - drop old table. * 6. Create new referencing table. * 7. Copy data to new referencing table. * 8. Drop temp table. * 9. Drop old table. * 10. Re-enable FK. */ QVERIFY(sqls.size() == 10); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE test2 .*", sqls[i++]); verifyRe("INSERT INTO test2.*SELECT.*FROM test;", sqls[i++]); verifyRe("CREATE TABLE sqlitestudio_temp_table.*AS SELECT.*FROM abc.*", sqls[i++]); verifyRe("DROP TABLE abc;", sqls[i++]); verifyRe("CREATE TABLE abc .*", sqls[i++]); verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase2() { db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); TableModifier mod(db, "test"); createTable->columns[1]->name = "newCol"; mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Rename to temp in 2 steps. * 3. Second step of renaming (drop). * 4. Create new. * 5. Copy data from temp to new one. * 6. Rename referencing table to temp name in 2 steps. * 7. Second step of renaming (drop). * 8. Create new referencing table. * 9. Copy data to new referencing table. * 10. Drop first temp table. * 11. Drop second temp table. * 12. Enable FK. */ QVERIFY(sqls.size() == 12); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE sqlitestudio_temp_table.*AS SELECT.*FROM test.*", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); verifyRe("CREATE TABLE test .*newCol.*", sqls[i++]); verifyRe("INSERT INTO test.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); verifyRe("CREATE TABLE sqlitestudio_temp_table.*AS SELECT.*FROM abc.*", sqls[i++]); verifyRe("DROP TABLE abc;", sqls[i++]); verifyRe("CREATE TABLE abc .*xyz text REFERENCES test \\(newCol\\).*", sqls[i++]); verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase3() { db->exec("CREATE TABLE abc (id int, xyz text REFERENCES test (val));"); db->exec("CREATE INDEX i1 ON test (val);"); db->exec("CREATE INDEX i2 ON abc (id);"); TableModifier mod(db, "test"); createTable->table = "newTable"; createTable->columns[1]->name = "newCol"; mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Create new (with new name) * 3. Copy data to new * 4. Drop old table. * 5. Rename referencing table to temp name in two steps. * 6. Second step of renaming (drop). * 7. Create new referencing table. * 8. Copy data to new referencing table. * 9. Drop temp table. * 10. Re-create index i2. * 11. Re-create index i1 with new table name and column name. * 12. Disable FK. */ QVERIFY(sqls.size() == 12); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE newTable .*", sqls[i++]); verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); verifyRe("CREATE TABLE sqlitestudio_temp_table.*AS SELECT.*FROM abc.*", sqls[i++]); verifyRe("DROP TABLE abc;", sqls[i++]); verifyRe("CREATE TABLE abc .*xyz text REFERENCES newTable \\(newCol\\).*", sqls[i++]); verifyRe("INSERT INTO abc.*SELECT.*FROM sqlitestudio_temp_table.*", sqls[i++]); verifyRe("DROP TABLE sqlitestudio_temp_table.*", sqls[i++]); verifyRe("CREATE INDEX i2 ON abc \\(id\\);", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); verifyRe("CREATE INDEX i1 ON newTable \\(newCol\\);", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase4() { db->exec("CREATE TRIGGER t1 AFTER UPDATE OF Val ON Test BEGIN " "SELECT * FROM (SELECT Val FROM Test); " "UPDATE Test SET Val = (SELECT Val FROM Test) WHERE x = (SELECT Val FROM Test); " "INSERT INTO Test (val) VALUES (1); " "END;"); TableModifier mod(db, "test"); createTable->table = "newTable"; createTable->columns[1]->name = "newCol"; mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Create new (with new name) * 3. Copy data to new * 4. Drop old table. * 5. Recreate trigger with all subqueries updated. * 6. Enable FK. */ QVERIFY(sqls.size() == 6); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE newTable .*", sqls[i++]); verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); QVERIFY2("CREATE TRIGGER t1 AFTER UPDATE OF newCol ON newTable " "BEGIN " "SELECT * FROM (SELECT newCol FROM newTable); " "UPDATE newTable SET newCol = (SELECT newCol FROM newTable) WHERE x = (SELECT newCol FROM newTable); " "INSERT INTO newTable (newCol) VALUES (1); " "END;" == sqls[i++], "Trigger DDL incorrect."); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase5() { db->exec("CREATE VIEW v1 AS SELECT * FROM (SELECT Val FROM Test);"); db->exec("CREATE TRIGGER t1 INSTEAD OF INSERT ON v1 BEGIN SELECT 1; END;"); db->exec("CREATE TRIGGER t2 AFTER INSERT ON v1 BEGIN SELECT 1; END;"); db->exec("CREATE TRIGGER t3 INSTEAD OF INSERT ON test BEGIN SELECT 1; END;"); TableModifier mod(db, "test"); createTable->table = "newTable"; createTable->columns[1]->name = "newCol"; mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Create new (with new name) * 3. Copy data to new * 4. Drop old table. * 5. Drop old view. * 6. Recreate view with new column and table. * 7. Recreate trigger with all subqueries updated. * 8. Enable FK. */ QVERIFY(sqls.size() == 8); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE newTable .*", sqls[i++]); verifyRe("INSERT INTO newTable.*SELECT.*FROM test;", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); verifyRe("DROP VIEW v1;", sqls[i++]); verifyRe("CREATE VIEW v1 AS SELECT \\* FROM \\(SELECT newCol FROM newTable\\);", sqls[i++]); verifyRe("CREATE TRIGGER t1 INSTEAD OF INSERT ON v1 BEGIN SELECT 1; END;", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase6() { db->exec("CREATE VIEW v1 AS SELECT * FROM (SELECT Id, Val FROM Test);"); db->exec("CREATE TRIGGER t2 AFTER UPDATE OF Id, Val ON Test BEGIN SELECT Val, Val2 FROM Test; END;"); TableModifier mod(db, "test"); createTable->table = "newTable"; createTable->columns.removeAt(1); mod.alterTable(createTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Create new (with new name) * 3. Copy data to new * 4. Drop old table. * 5. Recreate trigger with all subqueries updated. * 6. Drop view. * 7. Recreate view with new table referenced. * 8. Enable FK. */ QVERIFY(sqls.size() == 8); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE newTable \\(id int, val2 text\\);", sqls[i++]); verifyRe("INSERT INTO newTable \\(id, val2\\) SELECT id, val2 FROM test;", sqls[i++]); verifyRe("DROP TABLE test;", sqls[i++]); verifyRe("CREATE TRIGGER t2 AFTER UPDATE OF Id ON newTable BEGIN SELECT NULL, Val2 FROM newTable; END;", sqls[i++]); verifyRe("DROP VIEW v1;", sqls[i++]); verifyRe("CREATE VIEW v1 AS SELECT \\* FROM \\(SELECT Id, NULL FROM newTable\\);", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::testCase7() { static_qstring(ddl, "CREATE TABLE abc (col1 text, col2 as (col1 || 'x'));"); db->exec(ddl); Parser parser; Q_ASSERT(parser.parse(ddl)); Q_ASSERT(parser.getQueries().size() > 0); SqliteCreateTablePtr localCreateTable = parser.getQueries().first().dynamicCast(); Q_ASSERT(!createTable.isNull()); TableModifier mod(db, "abc"); localCreateTable->table = "newTable"; mod.alterTable(localCreateTable); QStringList sqls = mod.generateSqls(); /* * 1. Disable FK. * 2. Create new (with new name) * 3. Copy data to new * 4. Drop old table. * 5. Enable FK. */ QVERIFY(sqls.size() == 5); int i = 0; verifyRe("PRAGMA foreign_keys = 0;", sqls[i++]); verifyRe("CREATE TABLE newTable \\(col1 text, col2 AS \\(col1 \\|\\| 'x'\\)\\);", sqls[i++]); verifyRe("INSERT INTO newTable \\(col1\\) SELECT col1 FROM abc\\;", sqls[i++]); verifyRe("DROP TABLE abc;", sqls[i++]); verifyRe("PRAGMA foreign_keys = 1;", sqls[i++]); } void TableModifierTest::initTestCase() { initKeywords(); Lexer::staticInit(); } void TableModifierTest::init() { initMocks(); initUtilsSql(); db = new DbSqlite3Mock("testdb"); db->open(); db->exec(mainTableDdl); Parser parser; Q_ASSERT(parser.parse(mainTableDdl)); Q_ASSERT(parser.getQueries().size() > 0); createTable = parser.getQueries().first().dynamicCast(); Q_ASSERT(!createTable.isNull()); } void TableModifierTest::cleanup() { db->close(); delete db; db = nullptr; } QTEST_APPLESS_MAIN(TableModifierTest) #include "tst_tablemodifiertest.moc"