#include "dbversionconverter.h" #include "schemaresolver.h" #include "common/global.h" #include "parser/ast/sqlitealtertable.h" #include "parser/ast/sqliteanalyze.h" #include "parser/ast/sqliteattach.h" #include "parser/ast/sqlitebegintrans.h" #include "parser/ast/sqlitecommittrans.h" #include "parser/ast/sqlitecopy.h" #include "parser/ast/sqlitecreatevirtualtable.h" #include "parser/ast/sqlitedelete.h" #include "parser/ast/sqlitedetach.h" #include "parser/ast/sqlitedropindex.h" #include "parser/ast/sqlitedroptable.h" #include "parser/ast/sqlitedroptrigger.h" #include "parser/ast/sqlitedropview.h" #include "parser/ast/sqliteemptyquery.h" #include "parser/ast/sqliteinsert.h" #include "parser/ast/sqlitepragma.h" #include "parser/ast/sqlitereindex.h" #include "parser/ast/sqliterelease.h" #include "parser/ast/sqliterollback.h" #include "parser/ast/sqlitesavepoint.h" #include "parser/ast/sqliteupdate.h" #include "parser/ast/sqlitevacuum.h" #include "services/pluginmanager.h" #include "plugins/dbplugin.h" #include "services/dbmanager.h" #include #include #include #include DbVersionConverter::DbVersionConverter() { connect(this, SIGNAL(askUserForConfirmation()), this, SLOT(confirmConversion())); connect(this, SIGNAL(conversionSuccessful()), this, SLOT(registerDbAfterSuccessfulConversion())); } DbVersionConverter::~DbVersionConverter() { safe_delete(fullConversionConfig); safe_delete(resolver); } void DbVersionConverter::convert(Dialect from, Dialect to, Db* srcDb, const QString& targetFile, const QString& targetName, ConversionConfimFunction confirmFunc, ConversionErrorsConfimFunction errorsConfirmFunc) { safe_delete(fullConversionConfig); fullConversionConfig = new FullConversionConfig; fullConversionConfig->from = from; fullConversionConfig->to = to; fullConversionConfig->srcDb = srcDb; fullConversionConfig->confirmFunc = confirmFunc; fullConversionConfig->errorsConfirmFunc = errorsConfirmFunc; fullConversionConfig->targetFile = targetFile; fullConversionConfig->targetName = targetName; QtConcurrent::run(this, &DbVersionConverter::fullConvertStep1); } void DbVersionConverter::convert(Dialect from, Dialect to, Db* db) { if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) convert2To3(db); else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) convert3To2(db); else qCritical() << "Unsupported db conversion combination."; } void DbVersionConverter::convert3To2(Db* db) { reset(); this->db = db; targetDialect = Dialect::Sqlite2; convertDb(); } void DbVersionConverter::convert2To3(Db* db) { reset(); this->db = db; targetDialect = Dialect::Sqlite3; convertDb(); } QString DbVersionConverter::convert(Dialect from, Dialect to, const QString& sql) { if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) return convert2To3(sql); else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) return convert3To2(sql); else { qCritical() << "Unsupported db conversion combination."; return QString(); } } QString DbVersionConverter::convert3To2(const QString& sql) { QStringList result; for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite3)) result << convert3To2(query)->detokenize(); return result.join("\n"); } QString DbVersionConverter::convert2To3(const QString& sql) { QStringList result; for (SqliteQueryPtr query : parse(sql, Dialect::Sqlite2)) result << convert2To3(query)->detokenize(); return result.join("\n"); } SqliteQueryPtr DbVersionConverter::convert(Dialect from, Dialect to, SqliteQueryPtr query) { if (from == Dialect::Sqlite2 && to == Dialect::Sqlite3) return convert2To3(query); else if (from == Dialect::Sqlite3 && to == Dialect::Sqlite2) return convert3To2(query); else { qCritical() << "Unsupported db conversion combination."; return SqliteQueryPtr(); } } SqliteQueryPtr DbVersionConverter::convert3To2(SqliteQueryPtr query) { SqliteQueryPtr newQuery; switch (query->queryType) { case SqliteQueryType::AlterTable: errors << QObject::tr("SQLite 2 does not support 'ALTER TABLE' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::Analyze: errors << QObject::tr("SQLite 2 does not support 'ANAYLZE' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::Attach: newQuery = copyQuery(query); break; case SqliteQueryType::BeginTrans: newQuery = copyQuery(query); break; case SqliteQueryType::CommitTrans: newQuery = copyQuery(query); break; case SqliteQueryType::Copy: qWarning() << "COPY query passed to DbVersionConverter::convertToVersion2(). SQLite3 query should not have COPY statement."; newQuery = copyQuery(query); break; case SqliteQueryType::CreateIndex: newQuery = copyQuery(query); if (!modifyCreateIndexForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::CreateTable: newQuery = copyQuery(query); if (!modifyCreateTableForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::CreateTrigger: newQuery = copyQuery(query); if (!modifyCreateTriggerForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::CreateView: newQuery = copyQuery(query); if (!modifyCreateViewForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::CreateVirtualTable: newQuery = copyQuery(query); if (!modifyVirtualTableForVesion2(newQuery, newQuery.dynamicCast().data())) { errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } else { errors << QObject::tr("SQLite 2 does not support 'CREATE VIRTUAL TABLE' statement. The regular table can be created instead if you proceed."); } break; case SqliteQueryType::Delete: newQuery = copyQuery(query); if (!modifyDeleteForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::Detach: newQuery = copyQuery(query); break; case SqliteQueryType::DropIndex: newQuery = copyQuery(query); break; case SqliteQueryType::DropTable: newQuery = copyQuery(query); break; case SqliteQueryType::DropTrigger: newQuery = copyQuery(query); break; case SqliteQueryType::DropView: newQuery = copyQuery(query); break; case SqliteQueryType::Insert: newQuery = copyQuery(query); if (!modifyInsertForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::Pragma: newQuery = copyQuery(query); break; case SqliteQueryType::Reindex: errors << QObject::tr("SQLite 2 does not support 'REINDEX' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::Release: errors << QObject::tr("SQLite 2 does not support 'RELEASE' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::Rollback: newQuery = copyQuery(query); break; case SqliteQueryType::Savepoint: errors << QObject::tr("SQLite 2 does not support 'SAVEPOINT' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::Select: { newQuery = copyQuery(query); if (!modifySelectForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; } case SqliteQueryType::Update: newQuery = copyQuery(query); if (!modifyUpdateForVersion2(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::Vacuum: newQuery = copyQuery(query); break; case SqliteQueryType::UNDEFINED: qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion2()."; newQuery = SqliteEmptyQueryPtr::create(); break; case SqliteQueryType::EMPTY: newQuery = copyQuery(query); break; } if (!newQuery) { qCritical() << "Query type not matched in DbVersionConverter::convertToVersion2():" << static_cast(query->queryType); return SqliteQueryPtr(); } if (newQuery->queryType != SqliteQueryType::EMPTY) { newQuery->setSqliteDialect(Dialect::Sqlite2); newQueries << newQuery; } newQuery->rebuildTokens(); return newQuery; } SqliteQueryPtr DbVersionConverter::convert2To3(SqliteQueryPtr query) { SqliteQueryPtr newQuery; switch (query->queryType) { case SqliteQueryType::AlterTable: newQuery = copyQuery(query); qWarning() << "ALTER TABLE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ALTER TABLE statement."; break; case SqliteQueryType::Analyze: newQuery = copyQuery(query); qWarning() << "ANALYZE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have ANALYZE statement."; break; case SqliteQueryType::Attach: newQuery = copyQuery(query); break; case SqliteQueryType::BeginTrans: newQuery = copyQuery(query); if (!modifyBeginTransForVersion3(newQuery.dynamicCast().data())) { newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); } break; case SqliteQueryType::CommitTrans: newQuery = copyQuery(query); break; case SqliteQueryType::Copy: errors << QObject::tr("SQLite 3 does not support 'COPY' statement."); newQuery = SqliteEmptyQueryPtr::create(); storeErrorDiff(query.data()); break; case SqliteQueryType::CreateIndex: newQuery = copyQuery(query); break; case SqliteQueryType::CreateTable: newQuery = copyQuery(query); break; case SqliteQueryType::CreateTrigger: newQuery = copyQuery(query); break; case SqliteQueryType::CreateView: newQuery = copyQuery(query); break; case SqliteQueryType::CreateVirtualTable: newQuery = copyQuery(query); break; case SqliteQueryType::Delete: newQuery = copyQuery(query); break; case SqliteQueryType::Detach: newQuery = copyQuery(query); break; case SqliteQueryType::DropIndex: newQuery = copyQuery(query); break; case SqliteQueryType::DropTable: newQuery = copyQuery(query); break; case SqliteQueryType::DropTrigger: newQuery = copyQuery(query); break; case SqliteQueryType::DropView: newQuery = copyQuery(query); break; case SqliteQueryType::Insert: newQuery = copyQuery(query); break; case SqliteQueryType::Pragma: newQuery = copyQuery(query); break; case SqliteQueryType::Reindex: newQuery = copyQuery(query); qWarning() << "REINDEX query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have REINDEX statement."; break; case SqliteQueryType::Release: newQuery = copyQuery(query); qWarning() << "RELEASE query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have RELEASE statement."; break; case SqliteQueryType::Rollback: newQuery = copyQuery(query); break; case SqliteQueryType::Savepoint: newQuery = copyQuery(query); qWarning() << "SAVEPOINT query passed to DbVersionConverter::convertToVersion3(). SQLite2 query should not have SAVEPOINT statement."; break; case SqliteQueryType::Select: newQuery = copyQuery(query); break; case SqliteQueryType::Update: newQuery = copyQuery(query); break; case SqliteQueryType::Vacuum: newQuery = copyQuery(query); break; case SqliteQueryType::UNDEFINED: qWarning() << "UNDEFINED query type passed to DbVersionConverter::convertToVersion3()."; case SqliteQueryType::EMPTY: newQuery = copyQuery(query); break; } if (!newQuery) { qCritical() << "Query type not matched in DbVersionConverter::convertToVersion3():" << static_cast(query->queryType); return SqliteQueryPtr(); } if (newQuery->queryType != SqliteQueryType::EMPTY) { newQuery->setSqliteDialect(Dialect::Sqlite2); newQueries << newQuery; } return newQuery; } QList DbVersionConverter::parse(const QString& sql, Dialect dialect) { Parser parser(dialect); if (!parser.parse(sql)) { errors << QObject::tr("Could not parse statement: %1\nError details: %2").arg(sql, parser.getErrorString()); return QList(); } return parser.getQueries(); } bool DbVersionConverter::modifySelectForVersion2(SqliteSelect* select) { if (select->with) { errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("SELECT"); return false; } QString sql1 = getSqlForDiff(select); for (SqliteSelect::Core* core : select->coreSelects) { if (core->valuesMode) core->valuesMode = false; } if (!modifyAllIndexedColumnsForVersion2(select)) return false; if (!modifyAllExprsForVersion2(select)) return false; storeDiff(sql1, select); return true; } bool DbVersionConverter::modifyDeleteForVersion2(SqliteDelete* del) { if (del->with) { errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("DELETE"); return false; } QString sql1 = getSqlForDiff(del); del->indexedBy = QString::null; del->indexedByKw = false; del->notIndexedKw = false; if (!modifyAllExprsForVersion2(del)) return false; storeDiff(sql1, del); return true; } bool DbVersionConverter::modifyInsertForVersion2(SqliteInsert* insert) { if (insert->with) { errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("INSERT"); return false; } if (insert->defaultValuesKw) { errors << QObject::tr("SQLite 2 does not support the 'DEFAULT VALUES' clause in the 'INSERT' clause."); return false; } if (!insert->select) { qCritical() << "No SELECT substatement in INSERT when converting from SQLite 3 to 2."; return false; } QString sql1 = getSqlForDiff(insert); // Modifying SELECT deals with "VALUES" completely. if (!modifySelectForVersion2(insert->select)) return false; if (!modifyAllExprsForVersion2(insert)) return false; storeDiff(sql1, insert); return true; } bool DbVersionConverter::modifyUpdateForVersion2(SqliteUpdate* update) { if (update->with) { errors << QObject::tr("SQLite 2 does not support the 'WITH' clause. Cannot convert '%1' statement with that clause.").arg("UPDATE"); return false; } QString sql1 = getSqlForDiff(update); if (!modifyAllExprsForVersion2(update)) return false; update->indexedBy = QString::null; update->indexedByKw = false; update->notIndexedKw = false; storeDiff(sql1, update); return true; } bool DbVersionConverter::modifyCreateTableForVersion2(SqliteCreateTable* createTable) { QString sql1 = getSqlForDiff(createTable); if (!createTable->database.isNull()) createTable->database = QString::null; // Subselect if (createTable->select) { if (!modifySelectForVersion2(createTable->select)) return false; } // Table constraints QMutableListIterator tableConstrIt(createTable->constraints); while (tableConstrIt.hasNext()) { tableConstrIt.next(); if (tableConstrIt.value()->type == SqliteCreateTable::Constraint::PRIMARY_KEY) tableConstrIt.value()->autoincrKw = false; } // Column constraints QMutableListIterator tableColIt(createTable->columns); while (tableColIt.hasNext()) { tableColIt.next(); QMutableListIterator tableColConstrIt(tableColIt.value()->constraints); while (tableColConstrIt.hasNext()) { tableColConstrIt.next(); switch (tableColConstrIt.value()->type) { case SqliteCreateTable::Column::Constraint::COLLATE: // theoretically supported by SQLite2, but it raises error from SQLite2 when used case SqliteCreateTable::Column::Constraint::NAME_ONLY: { delete tableColConstrIt.value(); tableColConstrIt.remove(); break; } case SqliteCreateTable::Column::Constraint::DEFAULT: { if (!tableColConstrIt.value()->ctime.isNull()) { delete tableColConstrIt.value(); tableColConstrIt.remove(); } else tableColConstrIt.value()->name = QString::null; break; } case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: { tableColConstrIt.value()->autoincrKw = false; break; } case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: { QMutableListIterator condIt(tableColConstrIt.value()->foreignKey->conditions); while (condIt.hasNext()) { condIt.next(); if (condIt.value()->reaction == SqliteForeignKey::Condition::NO_ACTION && condIt.value()->action != SqliteForeignKey::Condition::MATCH) // SQLite 2 has no "NO ACTION" { condIt.remove(); } } break; } case SqliteCreateTable::Column::Constraint::NOT_NULL: case SqliteCreateTable::Column::Constraint::UNIQUE: case SqliteCreateTable::Column::Constraint::CHECK: case SqliteCreateTable::Column::Constraint::NULL_: case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: break; } } } if (!modifyAllIndexedColumnsForVersion2(createTable)) return false; // WITHOUT ROWID if (!createTable->withOutRowId.isNull()) createTable->withOutRowId = QString::null; storeDiff(sql1, createTable); return true; } bool DbVersionConverter::modifyCreateTriggerForVersion2(SqliteCreateTrigger* createTrigger) { QString sql = getSqlForDiff(createTrigger); if (!createTrigger->database.isNull()) createTrigger->database = QString::null; for (SqliteQuery* query : createTrigger->queries) { switch (query->queryType) { case SqliteQueryType::Delete: { if (!modifyDeleteForVersion2(dynamic_cast(query))) return false; break; } case SqliteQueryType::Update: { if (!modifyUpdateForVersion2(dynamic_cast(query))) return false; break; } case SqliteQueryType::Insert: { if (!modifyInsertForVersion2(dynamic_cast(query))) return false; break; } case SqliteQueryType::Select: { if (!modifySelectForVersion2(dynamic_cast(query))) return false; break; } default: qWarning() << "Unexpected query type in trigger:" << sqliteQueryTypeToString(query->queryType); break; } } storeDiff(sql, createTrigger); return true; } bool DbVersionConverter::modifyCreateIndexForVersion2(SqliteCreateIndex* createIndex) { QString sql1 = getSqlForDiff(createIndex); if (!createIndex->database.isNull()) createIndex->database = QString::null; if (createIndex->where) { delete createIndex->where; createIndex->where = nullptr; } if (!modifyAllIndexedColumnsForVersion2(createIndex->indexedColumns)) return false; storeDiff(sql1, createIndex); return true; } bool DbVersionConverter::modifyCreateViewForVersion2(SqliteCreateView* createView) { QString sql1 = getSqlForDiff(createView); if (!createView->database.isNull()) createView->database = QString::null; if (!modifySelectForVersion2(createView->select)) return false; storeDiff(sql1, createView); return true; } bool DbVersionConverter::modifyVirtualTableForVesion2(SqliteQueryPtr& query, SqliteCreateVirtualTable* createVirtualTable) { if (!resolver) return false; SqliteCreateTablePtr createTable = resolver->resolveVirtualTableAsRegularTable(createVirtualTable->database, createVirtualTable->table); if (!createTable) return false; QString sql = getSqlForDiff(createVirtualTable); storeDiff(sql, createTable.data()); query = createTable.dynamicCast(); return true; } bool DbVersionConverter::modifyAllExprsForVersion2(SqliteStatement* stmt) { QList allExprs = stmt->getAllTypedStatements(); for (SqliteExpr* expr : allExprs) { if (!modifySingleExprForVersion2(expr)) return false; } return true; } bool DbVersionConverter::modifySingleExprForVersion2(SqliteExpr* expr) { switch (expr->mode) { case SqliteExpr::Mode::null: case SqliteExpr::Mode::LITERAL_VALUE: case SqliteExpr::Mode::BIND_PARAM: case SqliteExpr::Mode::ID: case SqliteExpr::Mode::UNARY_OP: case SqliteExpr::Mode::BINARY_OP: case SqliteExpr::Mode::FUNCTION: case SqliteExpr::Mode::SUB_EXPR: case SqliteExpr::Mode::LIKE: case SqliteExpr::Mode::NULL_: case SqliteExpr::Mode::NOTNULL: case SqliteExpr::Mode::IS: case SqliteExpr::Mode::BETWEEN: case SqliteExpr::Mode::CASE: case SqliteExpr::Mode::RAISE: break; case SqliteExpr::Mode::CTIME: errors << QObject::tr("SQLite 2 does not support current date or time clauses in expressions."); return false; case SqliteExpr::Mode::IN: case SqliteExpr::Mode::SUB_SELECT: { if (!modifySelectForVersion2(expr->select)) return false; break; } case SqliteExpr::Mode::CAST: errors << QObject::tr("SQLite 2 does not support 'CAST' clause in expressions."); return false; case SqliteExpr::Mode::EXISTS: errors << QObject::tr("SQLite 2 does not support 'EXISTS' clause in expressions."); return false; case SqliteExpr::Mode::COLLATE: { if (dynamic_cast(expr->parentStatement())) { // This is the only case when SQLite2 parser puts this mode into expression, that is for sortorder break; } else { errors << QObject::tr("SQLite 2 does not support 'COLLATE' clause in expressions."); return false; } } } return true; } bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(SqliteStatement* stmt) { QList columns = stmt->getAllTypedStatements(); return modifyAllIndexedColumnsForVersion2(columns); } bool DbVersionConverter::modifyAllIndexedColumnsForVersion2(const QList columns) { for (SqliteIndexedColumn* idxCol : columns) { if (!modifySingleIndexedColumnForVersion2(idxCol)) return false; } return true; } bool DbVersionConverter::modifySingleIndexedColumnForVersion2(SqliteIndexedColumn* idxCol) { if (!idxCol->collate.isNull()) idxCol->collate = QString::null; return true; } bool DbVersionConverter::modifyBeginTransForVersion3(SqliteBeginTrans* begin) { QString sql1 = getSqlForDiff(begin); begin->onConflict = SqliteConflictAlgo::null; storeDiff(sql1, begin); return true; } bool DbVersionConverter::modifyCreateTableForVersion3(SqliteCreateTable* createTable) { QString sql1 = getSqlForDiff(createTable); // Column constraints QMutableListIterator tableColIt(createTable->columns); while (tableColIt.hasNext()) { tableColIt.next(); QMutableListIterator tableColConstrIt(tableColIt.value()->constraints); while (tableColConstrIt.hasNext()) { tableColConstrIt.next(); switch (tableColConstrIt.value()->type) { case SqliteCreateTable::Column::Constraint::CHECK: { tableColConstrIt.value()->onConflict = SqliteConflictAlgo::null; break; } case SqliteCreateTable::Column::Constraint::NAME_ONLY: case SqliteCreateTable::Column::Constraint::DEFAULT: case SqliteCreateTable::Column::Constraint::PRIMARY_KEY: case SqliteCreateTable::Column::Constraint::NOT_NULL: case SqliteCreateTable::Column::Constraint::UNIQUE: case SqliteCreateTable::Column::Constraint::COLLATE: case SqliteCreateTable::Column::Constraint::FOREIGN_KEY: case SqliteCreateTable::Column::Constraint::NULL_: case SqliteCreateTable::Column::Constraint::DEFERRABLE_ONLY: break; } } } storeDiff(sql1, createTable); return true; } QString DbVersionConverter::getSqlForDiff(SqliteStatement* stmt) { stmt->rebuildTokens(); return stmt->detokenize(); } void DbVersionConverter::storeDiff(const QString& sql1, SqliteStatement* stmt) { stmt->rebuildTokens(); QString sql2 = stmt->detokenize(); if (sql1 != sql2) diffList << QPair(sql1, sql2); } void DbVersionConverter::storeErrorDiff(SqliteStatement* stmt) { stmt->rebuildTokens(); diffList << QPair(stmt->detokenize(), ""); } void DbVersionConverter::reset() { db = nullptr; targetDialect = Dialect::Sqlite3; diffList.clear(); newQueries.clear(); errors.clear(); safe_delete(resolver); } QList DbVersionConverter::getSupportedVersions() const { QList versions; for (Db* db : getAllPossibleDbInstances()) { versions << db->getDialect(); delete db; } return versions; } QStringList DbVersionConverter::getSupportedVersionNames() const { QStringList versions; for (Db* db : getAllPossibleDbInstances()) { versions << db->getTypeLabel(); delete db; } return versions; } void DbVersionConverter::fullConvertStep1() { convert(fullConversionConfig->from, fullConversionConfig->to, fullConversionConfig->srcDb); emit askUserForConfirmation(); } void DbVersionConverter::fullConvertStep2() { QFile outputFile(fullConversionConfig->targetFile); if (outputFile.exists() && !outputFile.remove()) { emit conversionFailed(tr("Target file exists, but could not be overwritten.")); return; } Db* db = nullptr; Db* tmpDb = nullptr; for (DbPlugin* plugin : PLUGINS->getLoadedPlugins()) { tmpDb = plugin->getInstance("", ":memory:", QHash()); if (tmpDb->initAfterCreated() && tmpDb->getDialect() == fullConversionConfig->to) db = plugin->getInstance(fullConversionConfig->targetName, fullConversionConfig->targetFile, QHash()); delete tmpDb; if (db) break; } if (!db) { emit conversionFailed(tr("Could not find proper database plugin to create target database.")); return; } if (checkForInterrupted(db, false)) return; if (!db->open()) { emit conversionFailed(db->getErrorText()); return; } if (!db->begin()) { conversionError(db, db->getErrorText()); return; } QStringList tables; if (!fullConvertCreateObjectsStep1(db, tables)) return; // error handled inside if (checkForInterrupted(db, true)) return; if (!fullConvertCopyData(db, tables)) return; // error handled inside if (checkForInterrupted(db, true)) return; if (!fullConvertCreateObjectsStep2(db)) return; // error handled inside if (checkForInterrupted(db, true)) return; if (!db->commit()) { conversionError(db, db->getErrorText()); return; } emit conversionSuccessful(); } bool DbVersionConverter::fullConvertCreateObjectsStep1(Db* db, QStringList& tables) { SqlQueryPtr result; SqliteCreateTablePtr createTable; for (const SqliteQueryPtr& query : getConverted()) { // Triggers and indexes are created in step2, after data was copied, to avoid invoking triggers // and speed up copying data. if (query->queryType == SqliteQueryType::CreateTrigger || query->queryType == SqliteQueryType::CreateIndex) continue; createTable = query.dynamicCast(); if (!createTable.isNull()) tables << createTable->table; result = db->exec(query->detokenize()); if (result->isError()) { conversionError(db, result->getErrorText()); return false; } } return true; } bool DbVersionConverter::fullConvertCreateObjectsStep2(Db* db) { SqlQueryPtr result; for (const SqliteQueryPtr& query : getConverted()) { // Creating only triggers and indexes if (query->queryType != SqliteQueryType::CreateTrigger && query->queryType != SqliteQueryType::CreateIndex) continue; result = db->exec(query->detokenize()); if (result->isError()) { conversionError(db, result->getErrorText()); return false; } if (checkForInterrupted(db, true)) return false; } return true; } bool DbVersionConverter::fullConvertCopyData(Db* db, const QStringList& tables) { static const QString selectSql = QStringLiteral("SELECT * FROM %1;"); static const QString insertSql = QStringLiteral("INSERT INTO %1 VALUES (%2);"); Dialect srcDialect = fullConversionConfig->srcDb->getDialect(); Dialect trgDialect = db->getDialect(); QString srcTable; QString trgTable; SqlQueryPtr result; SqlQueryPtr dataResult; SqlResultsRowPtr resultsRow; int i = 1; for (const QString& table : tables) { // Select data srcTable = wrapObjIfNeeded(table, srcDialect); trgTable = wrapObjIfNeeded(table, trgDialect); dataResult = fullConversionConfig->srcDb->exec(selectSql.arg(srcTable)); if (dataResult->isError()) { conversionError(db, dataResult->getErrorText()); return false; } if (checkForInterrupted(db, true)) return false; // Copy row by row i = 1; while (dataResult->hasNext()) { // Get row resultsRow = dataResult->next(); if (dataResult->isError()) { conversionError(db, dataResult->getErrorText()); return false; } // Insert row result = db->exec(insertSql.arg(trgTable, generateQueryPlaceholders(resultsRow->valueList().size())), resultsRow->valueList()); if (result->isError()) { conversionError(db, result->getErrorText()); return false; } if (i++ % 100 == 0 && checkForInterrupted(db, true)) return false; } } return true; } bool DbVersionConverter::checkForInterrupted(Db* db, bool rollback) { if (isInterrupted()) { conversionInterrupted(db, rollback); return true; } return false; } QList DbVersionConverter::getAllPossibleDbInstances() const { QList dbList; Db* db = nullptr; for (DbPlugin* plugin : PLUGINS->getLoadedPlugins()) { db = plugin->getInstance("", ":memory:", QHash()); if (!db->initAfterCreated()) continue; dbList << db; } return dbList; } QString DbVersionConverter::generateQueryPlaceholders(int argCount) { QStringList args; for (int i = 0; i < argCount; i++) args << "?"; return args.join(", "); } void DbVersionConverter::sortConverted() { qSort(newQueries.begin(), newQueries.end(), [](const SqliteQueryPtr& q1, const SqliteQueryPtr& q2) -> bool { if (!q1 || !q2) { if (!q1) return false; else return true; } if (q1->queryType == q2->queryType) return false; if (q1->queryType == SqliteQueryType::CreateTable) return true; if (q1->queryType == SqliteQueryType::CreateView && q2->queryType != SqliteQueryType::CreateTable) return true; return false; }); } void DbVersionConverter::setInterrupted(bool value) { QMutexLocker locker(&interruptMutex); interrupted = value; } bool DbVersionConverter::isInterrupted() { QMutexLocker locker(&interruptMutex); return interrupted; } void DbVersionConverter::conversionInterrupted(Db* db, bool rollback) { emit conversionAborted(); if (rollback) db->rollback(); db->close(); QFile file(fullConversionConfig->targetFile); if (file.exists()) file.remove(); } void DbVersionConverter::conversionError(Db* db, const QString& errMsg) { emit conversionFailed(tr("Error while converting database: %1").arg(errMsg)); db->rollback(); db->close(); QFile file(fullConversionConfig->targetFile); if (file.exists()) file.remove(); } void DbVersionConverter::confirmConversion() { if (!errors.isEmpty() && !fullConversionConfig->errorsConfirmFunc(errors)) { emit conversionAborted(); return; } if (!diffList.isEmpty() && !fullConversionConfig->confirmFunc(diffList)) { emit conversionAborted(); return; } QtConcurrent::run(this, &DbVersionConverter::fullConvertStep2); } void DbVersionConverter::registerDbAfterSuccessfulConversion() { DBLIST->addDb(fullConversionConfig->targetName, fullConversionConfig->targetFile); } void DbVersionConverter::interrupt() { setInterrupted(true); } const QList >& DbVersionConverter::getDiffList() const { return diffList; } const QSet& DbVersionConverter::getErrors() const { return errors; } const QList&DbVersionConverter::getConverted() const { return newQueries; } QStringList DbVersionConverter::getConvertedSqls() const { QStringList sqls; for (SqliteQueryPtr query : newQueries) sqls << query->detokenize(); return sqls; } void DbVersionConverter::convertDb() { resolver = new SchemaResolver(db); resolver->setIgnoreSystemObjects(true); StrHash parsedObjects = resolver->getAllParsedObjects(); for (SqliteQueryPtr query : parsedObjects.values()) { switch (targetDialect) { case Dialect::Sqlite2: convert3To2(query); break; case Dialect::Sqlite3: convert2To3(query); break; } } sortConverted(); }