diff --git a/CMakeLists.txt b/CMakeLists.txt index 691f33920..6c40f4545 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,264 +1,264 @@ cmake_minimum_required(VERSION 3.0 FATAL_ERROR) find_package(ECM 1.8.0 REQUIRED NOMODULE) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(SetKexiCMakePolicies NO_POLICY_SCOPE) include(SetKexiVersionInfo) project(Kexi VERSION ${PROJECT_VERSION}) # ECM include(ECMAddAppIcon) include(ECMAddTests) include(ECMGenerateHeaders) include(ECMInstallIcons) include(ECMMarkAsTest) include(ECMMarkNonGuiExecutable) include(ECMPoQmTools) include(ECMSetupVersion) include(KDEInstallDirs) include(KDECMakeSettings NO_POLICY_SCOPE) include(KDECompilerSettings NO_POLICY_SCOPE) # Own include(KexiMacros) include(KexiAddIconsRccFile) include(KexiGenerateDependencyGraph) ensure_out_of_source_build("Please refer to the build instruction https://community.kde.org/Kexi/Building") get_git_revision_and_branch() detect_release_build() ####################### ######################## ## Productset setting ## ######################## ####################### # For predefined productsets see the definitions in KexiProducts.cmake and # in the files in the folder cmake/productsets. # Finding out the products & features to build is done in 5 steps: # 1. have the user define the products/features wanted, by giving a productset # 2. estimate all additional required products/features # 3. estimate which of the products/features can be build by external deps # 4. find which products/features have been temporarily disabled due to problems # 5. estimate which of the products/features can be build by internal deps # get the special macros include(CalligraProductSetMacros) set(PRODUCTSET_DEFAULT "DESKTOP") if(NOT PRODUCTSET OR PRODUCTSET STREQUAL "ALL") # Force the default set also when it is "ALL" because we can't build both desktop and mobile set(PRODUCTSET ${PRODUCTSET_DEFAULT} CACHE STRING "Set of products/features to build" FORCE) endif() # get the definitions of products, features and product sets include(KexiProducts.cmake) if (RELEASE_BUILD) set(KEXI_SHOULD_BUILD_STAGING FALSE) else () set(KEXI_SHOULD_BUILD_STAGING TRUE) endif () # finally choose products/features to build calligra_set_productset(${PRODUCTSET}) ########################## ########################### ## Look for Qt, KF5 ## ########################### ########################## set(REQUIRED_KF5_VERSION 5.16.0) set(REQUIRED_KF5_COMPONENTS Archive Codecs Config ConfigWidgets CoreAddons GuiAddons I18n IconThemes ItemViews WidgetsAddons TextWidgets XmlGui ) if(SHOULD_BUILD_KEXI_DESKTOP_APP) list(APPEND REQUIRED_KF5_COMPONENTS Completion KIO TextEditor TextWidgets ) endif() find_package(KF5 ${REQUIRED_KF5_VERSION} REQUIRED COMPONENTS ${REQUIRED_KF5_COMPONENTS}) find_package(KF5 ${REQUIRED_KF5_VERSION} QUIET OPTIONAL_COMPONENTS Crash) macro_bool_to_01(KF5Crash_FOUND HAVE_KCRASH) macro_log_feature(${KF5Crash_FOUND} "KCrash" "KDE's Crash Handler" "https://api.kde.org/frameworks/kcrash/html" FALSE "" "Optionally used to provide crash reporting on Linux") set(REQUIRED_QT_VERSION 5.4.0) find_package(Qt5 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Widgets Xml Network PrintSupport Test) find_package(Qt5 ${REQUIRED_QT_VERSION} COMPONENTS UiTools WebKit WebKitWidgets) # use sane compile flags add_definitions( -DQT_NO_CAST_TO_ASCII -DQT_NO_SIGNALS_SLOTS_KEYWORDS -DQT_NO_URL_CAST_FROM_STRING -DQT_STRICT_ITERATORS -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_QSTRINGBUILDER ) # only with COMPILING_TESTS definition will all the FOO_TEST_EXPORT macros do something # TODO: check if this can be moved to only those places which make use of it, # to reduce global compiler definitions that would trigger a recompile of # everything on a change (like adding/removing tests to/from the build) macro_bool_to_01(BUILD_TESTING COMPILING_TESTS) # overcome some platform incompatibilities if(WIN32) find_package(KDEWin REQUIRED) endif() # set custom Kexi plugin installdir set(KEXI_PLUGIN_INSTALL_DIR ${PLUGIN_INSTALL_DIR}/${KEXI_BASE_PATH}) # TEMPORARY: for initial Qt5/KF5 build porting phase deprecation warnings are only annoying noise # remove once code porting phase starts, perhaps first locally in product subdirs #if (CMAKE_COMPILER_IS_GNUCXX OR CMAKE_COMPILER_IS_GNUC) # add_definitions(-Wno-deprecated -Wno-deprecated-declarations) #endif () ########################### ############################ ## Required dependencies ## ############################ ########################### set(KEXI_LIBS_MIN_VERSION 3.0.90) ## ## Test for KDb ## option(KEXI_DEBUG_GUI "Debugging GUI for Kexi (requires KDB_DEBUG_GUI to be set too)" OFF) if(KEXI_DEBUG_GUI) set(KDB_REQUIRED_COMPONENTS DEBUG_GUI) endif() -find_package(KDb ${KEXI_LIBS_MIN_VERSION} REQUIRED COMPONENTS ${KDB_REQUIRED_COMPONENTS}) +find_package(KDb 3.0.91 REQUIRED COMPONENTS ${KDB_REQUIRED_COMPONENTS}) macro_log_feature(KDb_FOUND "KDb" "A database connectivity and creation framework" "http://community.kde.org/KDb" FALSE "" "Required by Kexi for data handling") ## ## Test for KReport ## find_package(KReport ${KEXI_LIBS_MIN_VERSION} REQUIRED) if (KReport_FOUND) if(NOT KREPORT_SCRIPTING) message(FATAL_ERROR "Kexi requires KReport package with scripting support enabled (KREPORT_SCRIPTING)") endif() endif() ## ## Test for KPropertyWidgets ## if(SHOULD_BUILD_KEXI_DESKTOP_APP) find_package(KPropertyWidgets ${KEXI_LIBS_MIN_VERSION} REQUIRED COMPONENTS KF) macro_log_feature(KPropertyWidgets_FOUND "KPropertyWidgets" "A property editing framework with editor widget" "http://community.kde.org/KProperty" FALSE "" "Required by Kexi Desktop") else() # only KPropertyCore find_package(KPropertyCore ${KEXI_LIBS_MIN_VERSION} REQUIRED COMPONENTS KF) macro_log_feature(KPropertyCore_FOUND "KPropertyCore" "A property editing framework with editor widget" "http://community.kde.org/KProperty" FALSE "" "Required by Kexi Mobile") endif() include(CheckIfQtGuiCanBeExecuted) if(SHOULD_BUILD_KEXI_DESKTOP_APP) include(CheckGlobalBreezeIcons) endif() ########################### ############################ ## Optional dependencies ## ############################ ########################### ## ## Test for marble ## set(MARBLE_MIN_VERSION "0.19.2") find_package(KexiMarble) if(NOT MARBLE_FOUND) set(MARBLE_INCLUDE_DIR "") else() set(HAVE_MARBLE TRUE) endif() macro_log_feature(MARBLE_FOUND "Marble" "KDE World Globe Widget library" "https://marble.kde.org/" FALSE "${MARBLE_MIN_VERSION}" "Required by Kexi form map widget") ## ## Test for Qt WebKitWidgets ## #TODO switch to Qt WebEngine macro_bool_to_01(Qt5WebKitWidgets_FOUND HAVE_QTWEBKITWIDGETS) macro_log_feature(Qt5WebKitWidgets_FOUND "Qt WebkitWidgets" "QWidgets module for Webkit, the HTML engine." "http://qt.io" FALSE "" "Required by Kexi web form widget") ################## ################### ## Helper macros ## ################### ################## include(MacroKexiAddBenchmark) include(MacroKexiAddTest) ############################################# #### Temporarily broken products #### ############################################# # If a product does not build due to some temporary brokeness disable it here, # by calling calligra_disable_product with the product id and the reason, # e.g.: # calligra_disable_product(APP_KEXI "isn't buildable at the moment") ############################################# #### Calculate buildable products #### ############################################# calligra_drop_unbuildable_products() ############################################# #### Setup product-depending vars #### ############################################# ################### #################### ## Subdirectories ## #################### ################### add_subdirectory(src) if(SHOULD_BUILD_DOC) find_package(KF5 ${KF5_DEP_VERSION} REQUIRED COMPONENTS DocTools) add_subdirectory(doc) endif() # non-app directories are moved here because they can depend on SHOULD_BUILD_{appname} variables set above add_subdirectory(cmake) if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install(po) endif() macro_display_feature_log() calligra_product_deps_report("product_deps") calligra_log_should_build() diff --git a/cmake/modules/SetKexiVersionInfo.cmake b/cmake/modules/SetKexiVersionInfo.cmake index e437f09c8..3b274f6d7 100644 --- a/cmake/modules/SetKexiVersionInfo.cmake +++ b/cmake/modules/SetKexiVersionInfo.cmake @@ -1,77 +1,77 @@ # Copyright (C) 2003-2016 Jarosław Staniek # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # Define common versions of Kexi components used to generate KexiVersion.h # update these version for every release: set(PROJECT_VERSION_STRING "3.1 Alpha") # Custom name such as "3.1 Alpha" set(PROJECT_STABLE_VERSION_MAJOR 3) # 3 for 3.x, 4 for 4.x, etc. set(PROJECT_STABLE_VERSION_MINOR 1) # 0 for 3.0, 1 for 3.1, etc. -set(PROJECT_VERSION_RELEASE 90) # 90 for Alpha, increase for next test releases, set 0 for first Stable, etc. +set(PROJECT_VERSION_RELEASE 91) # 90 for Alpha, increase for next test releases, set 0 for first Stable, etc. set(KEXI_ALPHA 1) # uncomment only for Alpha #set(KEXI_BETA 1) # uncomment only for Beta #set(KEXI_RC 1) # uncomment only for RC set(KEXI_YEAR 2017) # update every year # -- do not edit below this line -- set(KEXI_CUSTOM_DISTRIBUTION_VERSION "" CACHE STRING "Custom name of Kexi version useful to construct co-installabile releases. Any nonempty directory name is accepted. If specified it will be used in KEXI_DISTRIBUTION_VERSION. If not specified, KEXI_DISTRIBUTION_VERSION will be set to PROJECT_STABLE_VERSION_MAJOR.PROJECT_STABLE_VERSION_MINOR.") if(KEXI_CUSTOM_DISTRIBUTION_VERSION STREQUAL "") set(KEXI_DISTRIBUTION_VERSION "${PROJECT_STABLE_VERSION_MAJOR}.${PROJECT_STABLE_VERSION_MINOR}") else() set(KEXI_DISTRIBUTION_VERSION "${KEXI_CUSTOM_DISTRIBUTION_VERSION}") endif() # Relative path name useful to construct co-installabile file names and paths set(KEXI_BASE_PATH "kexi/${KEXI_DISTRIBUTION_VERSION}") if(NOT DEFINED KEXI_ALPHA AND NOT DEFINED KEXI_BETA AND NOT DEFINED KEXI_RC) set(KEXI_STABLE 1) endif() # PROJECT_VERSION_MAJOR is the same as PROJECT_STABLE_VERSION_MAJOR but for unstable x.0 # x is decreased by one, e.g. 3.0 Beta is 2.99. if(NOT DEFINED KEXI_STABLE AND PROJECT_STABLE_VERSION_MINOR EQUAL 0) math(EXPR PROJECT_VERSION_MAJOR "${PROJECT_STABLE_VERSION_MAJOR} - 1") else() math(EXPR PROJECT_VERSION_MAJOR ${PROJECT_STABLE_VERSION_MAJOR}) endif() # PROJECT_VERSION_MINOR is equal to PROJECT_STABLE_VERSION_MINOR for stable releases, # equal to 99 for x.0 unstable releases (e.g. it's 3.0 Beta has minor version 99), # and equal to PROJECT_STABLE_VERSION_MINOR-1 for unstable releases other than x.0. if(DEFINED KEXI_STABLE) set(PROJECT_VERSION_MINOR ${PROJECT_STABLE_VERSION_MINOR}) elseif(PROJECT_STABLE_VERSION_MINOR EQUAL 0) set(PROJECT_VERSION_MINOR 99) else() math(EXPR PROJECT_VERSION_MINOR "${PROJECT_STABLE_VERSION_MINOR} - 1") endif() # PROJECT_STABLE_VERSION_RELEASE is equal to PROJECT_VERSION_RELEASE for stable releases # and 0 for unstable ones. if(DEFINED KEXI_STABLE) set(PROJECT_STABLE_VERSION_RELEASE ${PROJECT_VERSION_RELEASE}) else() set(PROJECT_STABLE_VERSION_RELEASE 0) endif() set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_RELEASE}) message(STATUS "Kexi version \"${PROJECT_VERSION_STRING}\" (${PROJECT_VERSION}), distribution version \"${KEXI_DISTRIBUTION_VERSION}\"") # Define the generic version of the Kexi libraries here # This makes it easy to advance it when the next Kexi release comes. # 14 was the last GENERIC_PROJECT_LIB_VERSION_MAJOR of the previous Kexi series # (2.x) so we're starting with 15 in 3.x series. if(PROJECT_STABLE_VERSION_MAJOR EQUAL 3) math(EXPR GENERIC_PROJECT_LIB_VERSION_MAJOR "${PROJECT_STABLE_VERSION_MINOR} + 15") else() # let's make sure we won't forget to update the "15" message(FATAL_ERROR "Reminder: please update offset == 15 used to compute GENERIC_PROJECT_LIB_VERSION_MAJOR to something bigger") endif() set(GENERIC_PROJECT_LIB_VERSION "${GENERIC_PROJECT_LIB_VERSION_MAJOR}.0.0") set(GENERIC_PROJECT_LIB_SOVERSION "${GENERIC_PROJECT_LIB_VERSION_MAJOR}") diff --git a/src/core/kexiproject.cpp b/src/core/kexiproject.cpp index c7b63756b..ea5f23a76 100644 --- a/src/core/kexiproject.cpp +++ b/src/core/kexiproject.cpp @@ -1,1473 +1,1473 @@ /* This file is part of the KDE project Copyright (C) 2003 Lucijan Busch Copyright (C) 2003-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kexiproject.h" #include "kexiprojectdata.h" #include "kexipartmanager.h" #include "kexipartitem.h" #include "kexipartinfo.h" #include "kexipart.h" #include "KexiWindow.h" #include "KexiWindowData.h" #include "kexi.h" #include "kexiblobbuffer.h" #include "kexiguimsghandler.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //! @return a real plugin ID for @a pluginId and @a partMime //! for compatibility with Kexi 1.x static QString realPluginId(const QString &pluginId, const QString &partMime) { if (pluginId.startsWith(QLatin1String("http://"))) { // for compatibility with Kexi 1.x // part mime was used at the time return QLatin1String("org.kexi-project.") + QString(partMime).remove("kexi/"); } return pluginId; } class Q_DECL_HIDDEN KexiProject::Private { public: explicit Private(KexiProject *qq) : q(qq) , connection(0) , data(0) , tempPartItemID_Counter(-1) , sqlParser(0) , versionMajor(0) , versionMinor(0) , privateIDCounter(0) , itemsRetrieved(false) { } ~Private() { delete data; data = 0; delete sqlParser; foreach(KexiPart::ItemDict* dict, itemDicts) { qDeleteAll(*dict); dict->clear(); } qDeleteAll(itemDicts); qDeleteAll(unstoredItems); unstoredItems.clear(); } void savePluginId(const QString& pluginId, int typeId) { if (!typeIds.contains(pluginId) && !pluginIdsForTypeIds.contains(typeId)) { typeIds.insert(pluginId, typeId); pluginIdsForTypeIds.insert(typeId, pluginId); } //! @todo what to do with extra plugin IDs for the same type ID or extra type ID name for the plugin ID? } //! @return user name for the current project //! @todo the name is taken from connection but it also can be specified otherwise //! if the same connection data is shared by multiple users. This will be especially //! true for 3-tier architectures. QString userName() const { QString name = connection->data().userName(); return name.isNull() ? "" : name; } bool setNameOrCaption(KexiPart::Item* item, const QString* _newName, const QString* _newCaption) { q->clearResult(); if (data->userMode()) { return false; } KexiUtils::WaitCursor wait; QString newName; if (_newName) { newName = _newName->trimmed(); KDbMessageTitleSetter ts(q); if (newName.isEmpty()) { q->m_result = KDbResult(xi18n("Could not set empty name for this object.")); return false; } if (q->itemForPluginId(item->pluginId(), newName) != 0) { q->m_result = KDbResult( xi18nc("@info", "Could not use this name. Object %1 already exists.", newName)); return false; } } QString newCaption; if (_newCaption) { newCaption = _newCaption->trimmed(); } KDbMessageTitleSetter et(q, xi18nc("@info", "Could not rename object %1.", item->name())); if (!q->checkWritable()) return false; KexiPart::Part *part = q->findPartFor(*item); if (!part) return false; KDbTransactionGuard tg(connection); if (!tg.transaction().active()) { q->m_result = connection->result(); return false; } if (_newName) { if (!part->rename(item, newName)) { q->m_result = KDbResult(part->lastOperationStatus().description); q->m_result.setMessageTitle(part->lastOperationStatus().message); return false; } - if (!connection->executeVoidSQL(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2") + if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_name=%1 WHERE o_id=%2") .arg(connection->escapeString(newName)) .arg(connection->driver()->valueToSQL(KDbField::Integer, item->identifier())))) { q->m_result = connection->result(); return false; } } if (_newCaption) { - if (!connection->executeVoidSQL(KDbEscapedString("UPDATE kexi__objects SET o_caption=%1 WHERE o_id=%2") + if (!connection->executeSql(KDbEscapedString("UPDATE kexi__objects SET o_caption=%1 WHERE o_id=%2") .arg(connection->escapeString(newCaption)) .arg(connection->driver()->valueToSQL(KDbField::Integer, item->identifier())))) { q->m_result = connection->result(); return false; } } if (!tg.commit()) { q->m_result = connection->result(); return false; } QString oldName(item->name()); if (_newName) { item->setName(newName); emit q->itemRenamed(*item, oldName); } QString oldCaption(item->caption()); if (_newCaption) { item->setCaption(newCaption); emit q->itemCaptionChanged(*item, oldCaption); } return true; } KexiProject *q; //! @todo KEXI3 use equivalent of QPointer KDbConnection* connection; //! @todo KEXI3 use equivalent of QPointer or make KexiProjectData implicitly shared like KDbConnectionData KexiProjectData *data; QString error_title; KexiPart::MissingPartsList missingParts; QHash typeIds; QHash pluginIdsForTypeIds; //! a cache for item() method, indexed by plugin IDs QHash itemDicts; QSet unstoredItems; //! helper for getting unique //! temporary identifiers for unstored items int tempPartItemID_Counter; KDbParser* sqlParser; int versionMajor; int versionMinor; int privateIDCounter; //!< counter: ID for private "document" like Relations window bool itemsRetrieved; }; //--------------------------- KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler) : QObject(), KDbObject(), KDbResultable() , d(new Private(this)) { d->data = new KexiProjectData(pdata); setMessageHandler(handler); } KexiProject::KexiProject(const KexiProjectData& pdata, KDbMessageHandler* handler, KDbConnection* conn) : QObject(), KDbObject(), KDbResultable() , d(new Private(this)) { d->data = new KexiProjectData(pdata); setMessageHandler(handler); if (*d->data->connectionData() == d->connection->data()) d->connection = conn; else qWarning() << "passed connection's data (" << conn->data().toUserVisibleString() << ") is not compatible with project's conn. data (" << d->data->connectionData()->toUserVisibleString() << ")"; } KexiProject::~KexiProject() { closeConnection(); delete d; } KDbConnection *KexiProject::dbConnection() const { return d->connection; } KexiProjectData* KexiProject::data() const { return d->data; } int KexiProject::versionMajor() const { return d->versionMajor; } int KexiProject::versionMinor() const { return d->versionMinor; } tristate KexiProject::open(bool *incompatibleWithKexi) { Q_ASSERT(incompatibleWithKexi); KDbMessageGuard mg(this); return openInternal(incompatibleWithKexi); } tristate KexiProject::open() { KDbMessageGuard mg(this); return openInternal(0); } tristate KexiProject::openInternal(bool *incompatibleWithKexi) { if (!Kexi::partManager().infoList()) { m_result = Kexi::partManager().result(); return cancelled; } if (incompatibleWithKexi) *incompatibleWithKexi = false; //qDebug() << d->data->databaseName() << d->data->connectionData()->driverId(); KDbMessageTitleSetter et(this, xi18nc("@info", "Could not open project %1.", d->data->databaseName())); if (!d->data->connectionData()->databaseName().isEmpty()) { QFileInfo finfo(d->data->connectionData()->databaseName()); if (!finfo.exists()) { KMessageBox::sorry(0, xi18nc("@info", "Could not open project. " "The project file %1 does not exist.", QDir::toNativeSeparators(finfo.absoluteFilePath())), xi18nc("@title:window", "Could Not Open File")); return cancelled; } if (!d->data->isReadOnly() && !finfo.isWritable()) { if (KexiProject::askForOpeningNonWritableFileAsReadOnly(0, finfo)) { d->data->setReadOnly(true); } else { return cancelled; } } } if (!createConnection()) { qWarning() << "!createConnection()"; return false; } bool cancel = false; if (!d->connection->useDatabase(d->data->databaseName(), true, &cancel)) { m_result = d->connection->result(); if (cancel) { return cancelled; } qWarning() << "!d->connection->useDatabase() " << d->data->databaseName() << " " << d->data->connectionData()->driverId(); if (d->connection->result().code() == ERR_NO_DB_PROPERTY) { // //! @todo this is temporary workaround as we have no import driver for SQLite if (/*supported?*/ !d->data->connectionData()->driverId().contains("sqlite")) { // if (incompatibleWithKexi) *incompatibleWithKexi = true; } else { KDbMessageTitleSetter et(this, xi18nc("@info (don't add tags around %1, it's done already)", "Database project %1 does not " "appear to have been created using Kexi and cannot be opened. " "It is an SQLite file created using other tools.", KexiUtils::localizedStringToHtmlSubstring(d->data->infoString()))); m_result = d->connection->result(); } closeConnectionInternal(); return false; } m_result = d->connection->result(); closeConnectionInternal(); return false; } if (!initProject()) return false; return createInternalStructures(/*insideTransaction*/true); } tristate KexiProject::create(bool forceOverwrite) { KDbMessageGuard mg(this); KDbMessageTitleSetter et(this, xi18nc("@info", "Could not create project %1.", d->data->databaseName())); if (!createConnection()) return false; if (!checkWritable()) return false; if (d->connection->databaseExists(d->data->databaseName())) { if (!forceOverwrite) return cancelled; if (!d->connection->dropDatabase(d->data->databaseName())) { m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' dropped ---"; } if (!d->connection->createDatabase(d->data->databaseName())) { m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' created ---"; // and now: open if (!d->connection->useDatabase(d->data->databaseName())) { qWarning() << "--- DB '" << d->data->databaseName() << "' USE ERROR ---"; m_result = d->connection->result(); closeConnectionInternal(); return false; } //qDebug() << "--- DB '" << d->data->databaseName() << "' used ---"; // KDbTransaction trans = d->connection->beginTransaction(); if (trans.isNull()) return false; if (!createInternalStructures(/*!insideTransaction*/false)) return false; //add some metadata //! @todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KDbProperties props = d->connection->databaseProperties(); if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", xi18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version")) || !props.setValue("project_caption", d->data->caption()) || !props.setCaption("project_caption", xi18n("Project caption")) || !props.setValue("project_desc", d->data->description()) || !props.setCaption("project_desc", xi18n("Project description"))) { m_result = props.result(); return false; } if (trans.active() && !d->connection->commitTransaction(trans)) return false; // if (!Kexi::partManager().infoList()) { m_result = Kexi::partManager().result(); return cancelled; } return initProject(); } bool KexiProject::createInternalStructures(bool insideTransaction) { KDbTransactionGuard tg; if (insideTransaction) { tg.setTransaction(d->connection->beginTransaction()); if (tg.transaction().isNull()) return false; } //Get information about kexiproject version. //kexiproject version is a version of data layer above kexidb layer. KDbProperties props = d->connection->databaseProperties(); bool ok; int storedMajorVersion = props.value("kexiproject_major_ver").toInt(&ok); if (!ok) storedMajorVersion = 0; int storedMinorVersion = props.value("kexiproject_minor_ver").toInt(&ok); if (!ok) storedMinorVersion = 1; const tristate containsKexi__blobsTable = d->connection->containsTable("kexi__blobs"); if (~containsKexi__blobsTable) { return false; } int dummy; bool contains_o_folder_id = false; if (true == containsKexi__blobsTable) { const tristate res = d->connection->querySingleNumber( KDbEscapedString("SELECT COUNT(o_folder_id) FROM kexi__blobs"), &dummy, 0, false/*addLimitTo1*/); if (res == false) { m_result = d->connection->result(); } else if (res == true) { contains_o_folder_id = true; } } bool add_folder_id_column = false; //! @todo what about read-only db access? if (storedMajorVersion <= 0) { d->versionMajor = KEXIPROJECT_VERSION_MAJOR; d->versionMinor = KEXIPROJECT_VERSION_MINOR; //For compatibility for projects created before Kexi 1.0 beta 1: //1. no kexiproject_major_ver and kexiproject_minor_ver -> add them if (!d->connection->options()->isReadOnly()) { if (!props.setValue("kexiproject_major_ver", d->versionMajor) || !props.setCaption("kexiproject_major_ver", xi18n("Project major version")) || !props.setValue("kexiproject_minor_ver", d->versionMinor) || !props.setCaption("kexiproject_minor_ver", xi18n("Project minor version"))) { return false; } } if (true == containsKexi__blobsTable) { //! @todo what to do for readonly connections? Should we alter kexi__blobs in memory? if (!d->connection->options()->isReadOnly()) { if (!contains_o_folder_id) { add_folder_id_column = true; } } } } if (storedMajorVersion != d->versionMajor || storedMajorVersion != d->versionMinor) { //! @todo version differs: should we change something? d->versionMajor = storedMajorVersion; d->versionMinor = storedMinorVersion; } QScopedPointer t_blobs(new KDbInternalTableSchema("kexi__blobs")); t_blobs->addField(new KDbField("o_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned)); t_blobs->addField(new KDbField("o_data", KDbField::BLOB)); t_blobs->addField(new KDbField("o_name", KDbField::Text)); t_blobs->addField(new KDbField("o_caption", KDbField::Text)); t_blobs->addField(new KDbField("o_mime", KDbField::Text, KDbField::NotNull)); t_blobs->addField(new KDbField("o_folder_id", KDbField::Integer, 0, KDbField::Unsigned) //references kexi__gallery_folders.f_id //If null, the BLOB only points to virtual "All" folder //WILL BE USED in Kexi >=2.0 ); //*** create global BLOB container, if not present if (true == containsKexi__blobsTable) { if (add_folder_id_column && !d->connection->options()->isReadOnly()) { // 2. "kexi__blobs" table contains no "o_folder_id" column -> add it // (by copying table to avoid data loss) QScopedPointer kexi__blobsCopy( new KDbInternalTableSchema(*t_blobs)); //won't be not needed - will be physically renamed to kexi_blobs kexi__blobsCopy->setName("kexi__blobs__copy"); if (!d->connection->createTable(kexi__blobsCopy.data(), true /*replaceExisting*/)) { m_result = d->connection->result(); return false; } KDbInternalTableSchema *ts = kexi__blobsCopy.take(); // createTable() took ownerhip of kexi__blobsCopy // 2.1 copy data (insert 0's into o_folder_id column) - if (!d->connection->executeVoidSQL( + if (!d->connection->executeSql( KDbEscapedString("INSERT INTO kexi__blobs (o_data, o_name, o_caption, o_mime, o_folder_id) " "SELECT o_data, o_name, o_caption, o_mime, 0 FROM kexi__blobs")) // 2.2 remove the original kexi__blobs - || !d->connection->executeVoidSQL(KDbEscapedString("DROP TABLE kexi__blobs")) //lowlevel + || !d->connection->executeSql(KDbEscapedString("DROP TABLE kexi__blobs")) //lowlevel // 2.3 rename the copy back into kexi__blobs || !d->connection->alterTableName(ts, "kexi__blobs", false /* no replace */) ) { //(no need to drop the copy, ROLLBACK will drop it) m_result = d->connection->result(); return false; } } //! just insert this schema, proper table exists d->connection->createTable(t_blobs.take()); } else { if (!d->connection->options()->isReadOnly()) { if (!d->connection->createTable(t_blobs.data(), true/*replaceExisting*/)) { m_result = d->connection->result(); return false; } (void)t_blobs.take(); // createTable() took ownerhip of t_blobs } } //Store default part information. //Information for other parts (forms, reports...) are created on demand in KexiWindow::storeNewData() const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts"); if (~containsKexi__partsTable) { return false; } QScopedPointer t_parts(new KDbInternalTableSchema("kexi__parts")); t_parts->addField( new KDbField("p_id", KDbField::Integer, KDbField::PrimaryKey | KDbField::AutoInc, KDbField::Unsigned) ); t_parts->addField(new KDbField("p_name", KDbField::Text)); t_parts->addField(new KDbField("p_mime", KDbField::Text)); t_parts->addField(new KDbField("p_url", KDbField::Text)); if (true == containsKexi__partsTable) { //! just insert this schema d->connection->createTable(t_parts.take()); } else { if (!d->connection->options()->isReadOnly()) { bool partsTableOk = d->connection->createTable(t_parts.data(), true/*replaceExisting*/); if (!partsTableOk) { m_result = d->connection->result(); return false; } KDbInternalTableSchema *ts = t_parts.take(); // createTable() took ownerhip of t_parts QScopedPointer fl(ts->subList("p_id", "p_name", "p_mime", "p_url")); #define INSERT_RECORD(typeId, groupName, name) \ if (partsTableOk) { \ partsTableOk = d->connection->insertRecord(fl.data(), QVariant(int(KexiPart::typeId)), \ QVariant(groupName), \ QVariant("kexi/" name), QVariant("org.kexi-project." name)); \ if (partsTableOk) { \ d->savePluginId("org.kexi-project." name, int(KexiPart::typeId)); \ } \ } INSERT_RECORD(TableObjectType, "Tables", "table") INSERT_RECORD(QueryObjectType, "Queries", "query") INSERT_RECORD(FormObjectType, "Forms", "form") INSERT_RECORD(ReportObjectType, "Reports", "report") INSERT_RECORD(ScriptObjectType, "Scripts", "script") INSERT_RECORD(WebObjectType, "Web pages", "web") INSERT_RECORD(MacroObjectType, "Macros", "macro") #undef INSERT_RECORD if (!partsTableOk) { m_result = d->connection->result(); // note: kexi__parts object still exists because createTable() succeeded return false; } } } // User data storage const tristate containsKexi__userdataTable = d->connection->containsTable("kexi__userdata"); if (~containsKexi__userdataTable) { return false; } QScopedPointer t_userdata(new KDbInternalTableSchema("kexi__userdata")); t_userdata->addField(new KDbField("d_user", KDbField::Text, KDbField::NotNull)); t_userdata->addField(new KDbField("o_id", KDbField::Integer, KDbField::NotNull, KDbField::Unsigned)); t_userdata->addField(new KDbField("d_sub_id", KDbField::Text, KDbField::NotNull | KDbField::NotEmpty)); t_userdata->addField(new KDbField("d_data", KDbField::LongText)); if (true == containsKexi__userdataTable) { d->connection->createTable(t_userdata.take()); } else if (!d->connection->options()->isReadOnly()) { if (!d->connection->createTable(t_userdata.data(), true/*replaceExisting*/)) { m_result = d->connection->result(); return false; } (void)t_userdata.take(); // createTable() took ownerhip of t_userdata } if (insideTransaction) { if (tg.transaction().active() && !tg.commit()) { m_result = d->connection->result(); return false; } } return true; } bool KexiProject::createConnection() { clearResult(); KDbMessageGuard mg(this); if (d->connection) { return true; } KDbMessageTitleSetter et(this); KDbDriver *driver = Kexi::driverManager().driver(d->data->connectionData()->driverId()); if (!driver) { m_result = Kexi::driverManager().result(); return false; } KDbConnectionOptions connectionOptions; if (d->data->isReadOnly()) { connectionOptions.setReadOnly(true); } d->connection = driver->createConnection(*d->data->connectionData(), connectionOptions); if (!d->connection) { m_result = driver->result(); qWarning() << "error create connection: " << m_result; return false; } if (!d->connection->connect()) { m_result = d->connection->result(); qWarning() << "error connecting: " << m_result; delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return false; } //re-init BLOB buffer //! @todo won't work for subsequent connection KexiBLOBBuffer::setConnection(d->connection); return true; } bool KexiProject::closeConnectionInternal() { if (!m_result.isError()) { clearResult(); } if (!d->connection) { return true; } if (!d->connection->disconnect()) { if (!m_result.isError()) { m_result = d->connection->result(); } return false; } delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return true; } bool KexiProject::closeConnection() { clearResult(); KDbMessageGuard mg(this); if (!d->connection) return true; if (!d->connection->disconnect()) { m_result = d->connection->result(); return false; } delete d->connection; //this will also clear connection for BLOB buffer d->connection = 0; return true; } bool KexiProject::initProject() { //qDebug() << "checking project parts..."; if (!checkProject()) { return false; } // !@todo put more props. todo - creator, created date, etc. (also to KexiProjectData) KDbProperties props = d->connection->databaseProperties(); QString str(props.value("project_caption").toString()); if (!str.isEmpty()) d->data->setCaption(str); str = props.value("project_desc").toString(); if (!str.isEmpty()) d->data->setDescription(str); return true; } bool KexiProject::isConnected() { if (d->connection && d->connection->isDatabaseUsed()) return true; return false; } KexiPart::ItemDict* KexiProject::items(KexiPart::Info *i) { clearResult(); KDbMessageGuard mg(this); if (!i || !isConnected()) return 0; //trying in cache... KexiPart::ItemDict *dict = d->itemDicts.value(i->id()); if (dict) return dict; if (d->itemsRetrieved) return 0; if (!retrieveItems()) return 0; return items(i); // try again } bool KexiProject::retrieveItems() { d->itemsRetrieved = true; KDbCursor *cursor = d->connection->executeQuery( KDbEscapedString("SELECT o_id, o_name, o_caption, o_type FROM kexi__objects ORDER BY o_type")); if (!cursor) { m_result = d->connection->result(); return 0; } int recentTypeId = -1000; QString pluginId; KexiPart::ItemDict *dict = 0; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { bool ok; const int typeId = cursor->value(3).toInt(&ok); if (!ok || typeId <= 0) { qInfo() << "object of unknown type id" << cursor->value(3) << "id=" << cursor->value(0) << "name=" << cursor->value(1); continue; } if (recentTypeId == typeId) { if (pluginId.isEmpty()) // still the same unknown plugin ID continue; } else { // a new type ID: create another plugin items dict if it's an ID for a known type recentTypeId = typeId; pluginId = pluginIdForTypeId(typeId); if (pluginId.isEmpty()) continue; dict = new KexiPart::ItemDict(); d->itemDicts.insert(pluginId, dict); } const int ident = cursor->value(0).toInt(&ok); const QString objName(cursor->value(1).toString()); if (ok && (ident > 0) && !d->connection->isInternalTableSchema(objName) && KDb::isIdentifier(objName)) { KexiPart::Item *it = new KexiPart::Item(); it->setIdentifier(ident); it->setPluginId(pluginId); it->setName(objName); it->setCaption(cursor->value(2).toString()); dict->insert(it->identifier(), it); } //qDebug() << "ITEM ADDED == "<* staticObjectArgs) { clearResult(); KDbMessageGuard mg(this); if (viewMode != Kexi::DataViewMode && data()->userMode()) return 0; KDbMessageTitleSetter et(this); KexiPart::Part *part = findPartFor(*item); if (!part) return 0; KexiWindow *window = part->openInstance(parent, item, viewMode, staticObjectArgs); if (!window) { if (part->lastOperationStatus().error()) m_result = KDbResult(xi18nc("@info", "Opening object %1 failed.\n%2%3", item->name()) .arg(part->lastOperationStatus().message) .arg(part->lastOperationStatus().description) .replace("(I18N_ARGUMENT_MISSING)", " ")); // a hack until there's other solution return 0; } return window; } KexiWindow* KexiProject::openObject(QWidget* parent, const QString &pluginId, const QString& name, Kexi::ViewMode viewMode) { KexiPart::Item *it = itemForPluginId(pluginId, name); return it ? openObject(parent, it, viewMode) : 0; } bool KexiProject::checkWritable() { if (!d->connection->options()->isReadOnly()) return true; m_result = KDbResult(xi18n("This project is opened as read only.")); return false; } bool KexiProject::removeObject(KexiPart::Item *item) { Q_ASSERT(item); clearResult(); if (data()->userMode()) return false; KDbMessageTitleSetter et(this); if (!checkWritable()) return false; KexiPart::Part *part = findPartFor(*item); if (!part) return false; if (!item->neverSaved() && !part->remove(item)) { //! @todo check for errors return false; } if (!item->neverSaved()) { KDbTransactionGuard tg(d->connection); if (!tg.transaction().active()) { m_result = d->connection->result(); return false; } if (!d->connection->removeObject(item->identifier())) { m_result = d->connection->result(); return false; } if (!removeUserDataBlock(item->identifier())) { m_result = KDbResult(ERR_DELETE_SERVER_ERROR, xi18n("Could not remove object's user data.")); return false; } if (!tg.commit()) { m_result = d->connection->result(); return false; } } emit itemRemoved(*item); //now: remove this item from cache if (part->info()) { KexiPart::ItemDict *dict = d->itemDicts.value(part->info()->pluginId()); if (!(dict && dict->remove(item->identifier()))) d->unstoredItems.remove(item);//remove temp. } return true; } bool KexiProject::renameObject(KexiPart::Item *item, const QString& newName) { KDbMessageGuard mg(this); return d->setNameOrCaption(item, &newName, 0); } bool KexiProject::setObjectCaption(KexiPart::Item *item, const QString& newCaption) { KDbMessageGuard mg(this); return d->setNameOrCaption(item, 0, &newCaption); } KexiPart::Item* KexiProject::createPartItem(KexiPart::Info *info, const QString& suggestedCaption) { clearResult(); KDbMessageGuard mg(this); if (data()->userMode()) return 0; KDbMessageTitleSetter et(this); KexiPart::Part *part = Kexi::partManager().part(info); if (!part) { m_result = Kexi::partManager().result(); return 0; } KexiPart::ItemDict *dict = items(info); if (!dict) { dict = new KexiPart::ItemDict(); d->itemDicts.insert(info->pluginId(), dict); } QSet storedItemNames; foreach(KexiPart::Item* item, *dict) { storedItemNames.insert(item->name()); } QSet unstoredItemNames; foreach(KexiPart::Item* item, d->unstoredItems) { unstoredItemNames.insert(item->name()); } //find new, unique default name for this item int n; QString new_name; QString base_name; if (suggestedCaption.isEmpty()) { n = 1; base_name = part->instanceName(); } else { n = 0; //means: try not to add 'n' base_name = KDb::stringToIdentifier(suggestedCaption).toLower(); } base_name = KDb::stringToIdentifier(base_name).toLower(); do { new_name = base_name; if (n >= 1) new_name += QString::number(n); if (storedItemNames.contains(new_name)) { n++; continue; //stored exists! } if (!unstoredItemNames.contains(new_name)) break; //unstored doesn't exist n++; } while (n < 1000/*sanity*/); if (n >= 1000) return 0; QString new_caption(suggestedCaption.isEmpty() ? part->info()->name() : suggestedCaption); if (n >= 1) new_caption += QString::number(n); KexiPart::Item *item = new KexiPart::Item(); item->setIdentifier(--d->tempPartItemID_Counter); //temporary item->setPluginId(info->pluginId()); item->setName(new_name); item->setCaption(new_caption); item->setNeverSaved(true); d->unstoredItems.insert(item); return item; } KexiPart::Item* KexiProject::createPartItem(KexiPart::Part *part, const QString& suggestedCaption) { Q_ASSERT(part); return createPartItem(part->info(), suggestedCaption); } void KexiProject::deleteUnstoredItem(KexiPart::Item *item) { if (!item) return; d->unstoredItems.remove(item); delete item; } KDbParser* KexiProject::sqlParser() { if (!d->sqlParser) { if (!d->connection) return 0; d->sqlParser = new KDbParser(d->connection); } return d->sqlParser; } const char warningNoUndo[] = I18N_NOOP2("warning", "Entire project's data and design will be removed."); /*static*/ KexiProject* KexiProject::createBlankProject(bool *cancelled, const KexiProjectData& data, KDbMessageHandler* handler) { Q_ASSERT(cancelled); *cancelled = false; KexiProject *prj = new KexiProject(data, handler); tristate res = prj->create(false); if (~res) { //! @todo move to KexiMessageHandler if (KMessageBox::Yes != KMessageBox::warningYesNo(0, xi18nc("@info (don't add tags around %1, it's done already)", "The project %1 already exists." "Do you want to replace it with a new, blank one?" "%2", KexiUtils::localizedStringToHtmlSubstring(prj->data()->infoString()), xi18n(warningNoUndo)), QString(), KGuiItem(xi18nc("@action:button", "Replace")), KStandardGuiItem::cancel())) //! @todo add toUserVisibleString() for server-based prj { delete prj; *cancelled = true; return 0; } res = prj->create(true/*overwrite*/); } if (res != true) { delete prj; return 0; } //qDebug() << "new project created --- "; //! @todo Kexi::recentProjects().addProjectData( data ); return prj; } /*static*/ tristate KexiProject::dropProject(const KexiProjectData& data, KDbMessageHandler* handler, bool dontAsk) { if (!dontAsk && KMessageBox::Yes != KMessageBox::questionYesNo(0, xi18nc("@info", "Do you want to delete the project %1?" "%2", static_cast(&data)->name(), i18n(warningNoUndo)), QString(), KGuiItem(xi18nc("@action:button", "Delete Project"), koIconName("edit-delete")), KStandardGuiItem::no(), QString(), KMessageBox::Notify | KMessageBox::Dangerous)) { return cancelled; } KexiProject prj(data, handler); if (!prj.open()) return false; if (prj.dbConnection()->options()->isReadOnly()) { handler->showErrorMessage( KDbMessageHandler::Error, xi18n("Could not delete this project. Database connection for this project has been opened as read only.")); return false; } KDbMessageGuard mg(prj.dbConnection()->result(), handler); return prj.dbConnection()->dropDatabase(); } bool KexiProject::checkProject(const QString& singlePluginId) { clearResult(); //! @todo catch errors! if (!d->connection->isDatabaseUsed()) { m_result = d->connection->result(); return false; } const tristate containsKexi__partsTable = d->connection->containsTable("kexi__parts"); if (~containsKexi__partsTable) { return false; } if (true == containsKexi__partsTable) { // check if kexi__parts exists, if missing, createInternalStructures() will create it KDbEscapedString sql = KDbEscapedString("SELECT p_id, p_name, p_mime, p_url FROM kexi__parts ORDER BY p_id"); if (!singlePluginId.isEmpty()) { sql.append(KDbEscapedString(" WHERE p_url=%1").arg(d->connection->escapeString(singlePluginId))); } KDbCursor *cursor = d->connection->executeQuery(sql); if (!cursor) { m_result = d->connection->result(); return false; } bool saved = false; for (cursor->moveFirst(); !cursor->eof(); cursor->moveNext()) { const QString partMime(cursor->value(2).toString()); QString pluginId(cursor->value(3).toString()); pluginId = realPluginId(pluginId, partMime); if (pluginId == QLatin1String("uk.co.piggz.report")) { // compatibility pluginId = QLatin1String("org.kexi-project.report"); } KexiPart::Info *info = Kexi::partManager().infoForPluginId(pluginId); bool ok; const int typeId = cursor->value(0).toInt(&ok); if (!ok || typeId <= 0) { qWarning() << "Invalid type ID" << typeId << "; part with ID" << pluginId << "will not be used"; } if (info && ok && typeId > 0) { d->savePluginId(pluginId, typeId); saved = true; } else { KexiPart::MissingPart m; m.name = cursor->value(1).toString(); m.id = pluginId; d->missingParts.append(m); } } d->connection->deleteCursor(cursor); if (!saved && !singlePluginId.isEmpty()) { return false; // failure is single part class was not found } } return true; } int KexiProject::generatePrivateID() { return --d->privateIDCounter; } bool KexiProject::createIdForPart(const KexiPart::Info& info) { KDbMessageGuard mg(this); int typeId = typeIdForPluginId(info.pluginId()); if (typeId > 0) { return true; } // try again, perhaps the id is already created if (checkProject(info.pluginId())) { return true; } // Find first available custom part ID by taking the greatest // existing custom ID (if it exists) and adding 1. typeId = int(KexiPart::UserObjectType); tristate success = d->connection->querySingleNumber(KDbEscapedString("SELECT max(p_id) FROM kexi__parts"), &typeId); if (!success) { // Couldn't read part id's from the kexi__parts table m_result = d->connection->result(); return false; } else { // Got a maximum part ID, or there were no parts typeId = typeId + 1; typeId = qMax(typeId, (int)KexiPart::UserObjectType); } //this part's ID is not stored within kexi__parts: KDbTableSchema *ts = d->connection->tableSchema("kexi__parts"); if (!ts) { m_result = d->connection->result(); return false; } QScopedPointer fl(ts->subList("p_id", "p_name", "p_mime", "p_url")); //qDebug() << "fieldlist: " << (fl ? *fl : QString()); if (!fl) return false; //qDebug() << info.ptr()->untranslatedGenericName(); // QStringList sl = part()->info()->ptr()->propertyNames(); // for (QStringList::ConstIterator it=sl.constBegin();it!=sl.constEnd();++it) //qDebug() << *it << " " << part()->info()->ptr()->property(*it).toString(); if (!d->connection->insertRecord( fl.data(), QVariant(typeId), QVariant(info.untranslatedGroupName()), QVariant(QString::fromLatin1("kexi/") + info.typeName()/*ok?*/), QVariant(info.id() /*always ok?*/))) { m_result = d->connection->result(); return false; } //qDebug() << "insert success!"; d->savePluginId(info.id(), typeId); //qDebug() << "new id is: " << p_id; return true; } KexiPart::MissingPartsList KexiProject::missingParts() const { return d->missingParts; } static bool checkObjectId(const char* method, int objectID) { if (objectID <= 0) { qWarning() << method << ": Invalid objectID" << objectID; return false; } return true; } tristate KexiProject::loadUserDataBlock(int objectID, const QString& dataID, QString *dataString) { KDbMessageGuard mg(this); if (!checkObjectId("loadUserDataBlock", objectID)) { return false; } if (!d->connection->querySingleString( KDbEscapedString("SELECT d_data FROM kexi__userdata WHERE o_id=%1 AND ") .arg(d->connection->driver()->valueToSQL(KDbField::Integer, objectID)) + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName()) + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID), dataString)) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::storeUserDataBlock(int objectID, const QString& dataID, const QString &dataString) { KDbMessageGuard mg(this); if (!checkObjectId("storeUserDataBlock", objectID)) { return false; } KDbEscapedString sql = KDbEscapedString("SELECT kexi__userdata.o_id FROM kexi__userdata WHERE o_id=%1").arg(objectID); KDbEscapedString sql_sub = KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_user", d->userName()) + " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID); const tristate result = d->connection->resultExists(sql + " AND " + sql_sub); if (~result) { m_result = d->connection->result(); return false; } if (result == true) { - if (!d->connection->executeVoidSQL( + if (!d->connection->executeSql( KDbEscapedString("UPDATE kexi__userdata SET d_data=" + d->connection->driver()->valueToSQL(KDbField::LongText, dataString) + " WHERE o_id=" + QString::number(objectID) + " AND " + sql_sub))) { m_result = d->connection->result(); return false; } return true; } - if (!d->connection->executeVoidSQL( + if (!d->connection->executeSql( KDbEscapedString("INSERT INTO kexi__userdata (d_user, o_id, d_sub_id, d_data) VALUES (") + d->connection->driver()->valueToSQL(KDbField::Text, d->userName()) + ", " + QString::number(objectID) + ", " + d->connection->driver()->valueToSQL(KDbField::Text, dataID) + ", " + d->connection->driver()->valueToSQL(KDbField::LongText, dataString) + ")")) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::copyUserDataBlock(int sourceObjectID, int destObjectID, const QString &dataID) { KDbMessageGuard mg(this); if (!checkObjectId("storeUserDataBlock(sourceObjectID)", sourceObjectID)) { return false; } if (!checkObjectId("storeUserDataBlock(destObjectID)", destObjectID)) { return false; } if (sourceObjectID == destObjectID) return true; if (!removeUserDataBlock(destObjectID, dataID)) // remove before copying return false; KDbEscapedString sql = KDbEscapedString("INSERT INTO kexi__userdata SELECT t.d_user, %2, t.d_sub_id, t.d_data " "FROM kexi__userdata AS t WHERE d_user=%1 AND o_id=%3") .arg(d->connection->escapeString(d->userName())) .arg(d->connection->driver()->valueToSQL(KDbField::Integer, destObjectID)) .arg(d->connection->driver()->valueToSQL(KDbField::Integer, sourceObjectID)); if (!dataID.isEmpty()) { sql += " AND " + KDb::sqlWhere(d->connection->driver(), KDbField::Text, "d_sub_id", dataID); } - if (!d->connection->executeVoidSQL(sql)) { + if (!d->connection->executeSql(sql)) { m_result = d->connection->result(); return false; } return true; } bool KexiProject::removeUserDataBlock(int objectID, const QString& dataID) { KDbMessageGuard mg(this); if (!checkObjectId("removeUserDataBlock", objectID)) { return false; } if (dataID.isEmpty()) { if (!KDb::deleteRecords(d->connection, "kexi__userdata", "o_id", KDbField::Integer, objectID, "d_user", KDbField::Text, d->userName())) { m_result = d->connection->result(); return false; } else if (!KDb::deleteRecords(d->connection, "kexi__userdata", "o_id", KDbField::Integer, objectID, "d_user", KDbField::Text, d->userName(), "d_sub_id", KDbField::Text, dataID)) { m_result = d->connection->result(); return false; } } return true; } // static bool KexiProject::askForOpeningNonWritableFileAsReadOnly(QWidget *parent, const QFileInfo &finfo) { KGuiItem openItem(KStandardGuiItem::open()); openItem.setText(xi18n("Open As Read Only")); return KMessageBox::Yes == KMessageBox::questionYesNo( parent, xi18nc("@info", "Could not open file %1 for reading and writing." "Do you want to open the file as read only?", QDir::toNativeSeparators(finfo.filePath())), xi18nc("@title:window", "Could Not Open File" ), openItem, KStandardGuiItem::cancel(), QString()); } diff --git a/src/migration/KexiSqlMigrate.cpp b/src/migration/KexiSqlMigrate.cpp index 608e532cb..80b4acf7e 100644 --- a/src/migration/KexiSqlMigrate.cpp +++ b/src/migration/KexiSqlMigrate.cpp @@ -1,208 +1,207 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2006-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KexiSqlMigrate.h" #include #include #include #include #include #include #include #include KexiSqlMigrate::KexiSqlMigrate(const QString &kdbDriverId, QObject *parent, const QVariantList& args) : KexiMigration::KexiMigrate(parent, args) , m_kdbDriverId(kdbDriverId) { Q_ASSERT(!m_kdbDriverId.isEmpty()); } KexiSqlMigrate::~KexiSqlMigrate() { } KDbConnection* KexiSqlMigrate::drv_createConnection() { KDbDriverManager manager; KDbDriver *driver = manager.driver(m_kdbDriverId); if (!driver) { m_result = manager.result(); return nullptr; } KDbConnection *c = driver->createConnection(*data()->source); m_result = c ? KDbResult() : driver->result(); return c; } bool KexiSqlMigrate::drv_readTableSchema( const QString& originalName, KDbTableSchema *tableSchema) { //! @todo IDEA: ask for user input for captions //Perform a query on the table to get some data KDbEscapedString sql = KDbEscapedString("SELECT * FROM %1 LIMIT 0") .arg(sourceConnection()->escapeIdentifier(tableSchema->name())); - QScopedPointer result(sourceConnection()->executeSQL(sql)); + QSharedPointer result = sourceConnection()->prepareSql(sql); if (!result) { return false; } bool ok = true; const int fieldsCount = result->fieldsCount(); for (int i = 0; i < fieldsCount; i++) { KDbField *field = result->createField(originalName, i); if (field->type() == KDbField::InvalidType) { field->setType(userType(originalName + '.' + field->name())); } if (!tableSchema->addField(field)) { delete field; tableSchema->clear(); ok = false; break; } } return ok; } bool KexiSqlMigrate::drv_tableNames(QStringList *tableNames) { - QScopedPointer result(sourceConnection()->executeSQL(m_tableNamesSql)); + QSharedPointer result = sourceConnection()->prepareSql(m_tableNamesSql); if (!result || result->fieldsCount() < 1) { return false; } Q_FOREVER { - QScopedPointer record(result->fetchRecord()); + QSharedPointer record = result->fetchRecord(); if (!record) { if (result->lastResult().isError()) { return false; } break; } tableNames->append(record->stringValue(0)); } return true; } tristate KexiSqlMigrate::drv_queryStringListFromSQL( const KDbEscapedString& sqlStatement, int fieldIndex, QStringList *stringList, int numRecords) { - QScopedPointer result(sourceConnection()->executeSQL(sqlStatement)); + QSharedPointer result= sourceConnection()->prepareSql(sqlStatement); if (!result) { return true; } if (result->fieldsCount() < (fieldIndex+1)) { qWarning() << sqlStatement << ": fieldIndex too large (" << fieldIndex << "), expected 0.." << result->fieldsCount() - 1; return false; } for (int i = 0; numRecords == -1 || i < numRecords; i++) { - QScopedPointer record(result->fetchRecord()); + QSharedPointer record = result->fetchRecord(); if (!record) { if (numRecords != -1 || result->lastResult().isError()) { return false; } return true; } stringList->append(record->stringValue(fieldIndex)); } return true; } bool KexiSqlMigrate::drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter) { - QScopedPointer result( - sourceConnection()->executeSQL(KDbEscapedString("SELECT * FROM %1") - .arg(sourceConnection()->escapeIdentifier(srcTable)))); + QSharedPointer result = sourceConnection()->prepareSql( + KDbEscapedString("SELECT * FROM %1").arg(sourceConnection()->escapeIdentifier(srcTable))); if (!result) { return false; } const KDbQueryColumnInfo::Vector fieldsExpanded(dstTable->query()->fieldsExpanded()); const int numFields = qMin(fieldsExpanded.count(), result->fieldsCount()); Q_FOREVER { - QScopedPointer record(result->fetchRecord()); + QSharedPointer record = result->fetchRecord(); if (!record) { if (!result->lastResult().isError()) { break; } return false; } if (recordFilter) { - if (!(*recordFilter)(*record)) { + if (!(*recordFilter)(record)) { continue; } } QList vals; for(int i = 0; i < numFields; ++i) { const KDbSqlString s(record->cstringValue(i)); vals.append(KDb::cstringToVariant( s.string, fieldsExpanded.at(i)->field()->type(), 0, s.length)); } updateProgress(); if (recordFilter) { if (!(*recordFilter)(vals)) { continue; } } if (!destConn->insertRecord(dstTable, vals)) { return false; } } /*! @todo Check that wasn't an error, rather than end of result set */ return true; } bool KexiSqlMigrate::drv_getTableSize(const QString& table, quint64 *size) { Q_ASSERT(size); - QScopedPointer result( - sourceConnection()->executeSQL(KDbEscapedString("SELECT COUNT(*) FROM %1") - .arg(sourceConnection()->escapeIdentifier(table)))); + QSharedPointer result + = sourceConnection()->prepareSql(KDbEscapedString("SELECT COUNT(*) FROM %1") + .arg(sourceConnection()->escapeIdentifier(table))); if (!result) { return false; } - QScopedPointer record(result->fetchRecord()); + QSharedPointer record = result->fetchRecord(); if (!result || result->fieldsCount() == 0) { return false; } bool ok; quint64 value = record->toByteArray(0).toULongLong(&ok); if (!ok) { value = -1; } *size = value; return ok; } -KDbSqlResult* KexiSqlMigrate::drv_readFromTable(const QString& tableName) +QSharedPointer KexiSqlMigrate::drv_readFromTable(const QString& tableName) { - QScopedPointer result(sourceConnection()->executeSQL(KDbEscapedString("SELECT * FROM %1") - .arg(sourceConnection()->escapeIdentifier(tableName)))); + QSharedPointer result = sourceConnection()->prepareSql( + KDbEscapedString("SELECT * FROM %1").arg(sourceConnection()->escapeIdentifier(tableName))); if (!result || result->lastResult().isError()) { m_result = sourceConnection()->result(); qWarning() << m_result; - return nullptr; + result.clear(); } - return result.take(); + return result; } diff --git a/src/migration/KexiSqlMigrate.h b/src/migration/KexiSqlMigrate.h index b073f3216..fcfb53478 100644 --- a/src/migration/KexiSqlMigrate.h +++ b/src/migration/KexiSqlMigrate.h @@ -1,84 +1,84 @@ /* This file is part of the KDE project Copyright (C) 2004 Martin Ellis Copyright (C) 2006-2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXISQLMIGRATE_H #define KEXISQLMIGRATE_H #include //! @short A specialized class for a migrate plugin that imports native SQL databases into Kexi projects //! Compared to more generic KexiMigrate, KexiSqlMigrate implements needed methods using //! low level APIs of KDb. This is used for example by MySQL and PostgreSQL migration plugins. class KEXIMIGRATE_EXPORT KexiSqlMigrate : public KexiMigration::KexiMigrate { Q_OBJECT public: //! Constructs a migrate plugin that uses low level APIs of KDb driver @a kdbDriverId. //! @a kdbDriverId must not be empty. explicit KexiSqlMigrate(const QString &kdbDriverId, QObject *parent, const QVariantList& args = QVariantList()); virtual ~KexiSqlMigrate(); protected: //! Driver specific function to return table names bool drv_tableNames(QStringList *tablenames) Q_DECL_OVERRIDE; //! Driver specific implementation to read a table schema bool drv_readTableSchema( const QString& originalName, KDbTableSchema *tableSchema) Q_DECL_OVERRIDE; //! Driver specific connection creation KDbConnection* drv_createConnection() Q_DECL_OVERRIDE; /*! Fetches single string at column \a columnNumber for each record from result obtained by running \a sqlStatement. \a numRecords can be specified to limit number of records read. If \a numRecords is -1, all records are loaded. @see KexiMigrate::drv_queryStringListFromSQL() */ tristate drv_queryStringListFromSQL( const KDbEscapedString& sqlStatement, int columnNumber, QStringList *stringList, int numRecords = -1) Q_DECL_OVERRIDE; //! Copy a table from source DB to target DB (driver specific) bool drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter = nullptr) Q_DECL_OVERRIDE; bool drv_progressSupported() Q_DECL_OVERRIDE { return true; } bool drv_getTableSize(const QString& table, quint64* size) Q_DECL_OVERRIDE; //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_getTablesList( QStringList &list ); //! @todo move this somewhere to low level class (MIGRATION?) virtual bool drv_containsTable( const QString &tableName ); //Extended API //! Starts reading data from the source dataset's table - KDbSqlResult* drv_readFromTable(const QString & tableName) Q_DECL_OVERRIDE; + QSharedPointer drv_readFromTable(const QString & tableName) Q_DECL_OVERRIDE; const QString m_kdbDriverId; //! Used by drv_tableNames, should be filled in constructor of a subclass KDbEscapedString m_tableNamesSql; }; #endif diff --git a/src/migration/importtablewizard.cpp b/src/migration/importtablewizard.cpp index 75ff7e8e9..dd62fc7dd 100644 --- a/src/migration/importtablewizard.cpp +++ b/src/migration/importtablewizard.cpp @@ -1,759 +1,759 @@ /* This file is part of the KDE project Copyright (C) 2009 Adam Pigg Copyright (C) 2014-2016 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "importtablewizard.h" #include "migratemanager.h" #include "keximigrate.h" #include "keximigratedata.h" #include "AlterSchemaWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KexiMigration; #define RECORDS_FOR_PREVIEW 3 ImportTableWizard::ImportTableWizard ( KDbConnection* curDB, QWidget* parent, QMap* args, Qt::WindowFlags flags) : KAssistantDialog ( parent, flags ), m_args(args) { m_connection = curDB; m_migrateDriver = 0; m_prjSet = 0; m_importComplete = false; m_importWasCanceled = false; KexiMainWindowIface::global()->setReasonableDialogSize(this); setupIntroPage(); setupSrcConn(); setupSrcDB(); setupTableSelectPage(); setupAlterTablePage(); setupImportingPage(); setupProgressPage(); setupFinishPage(); setValid(m_srcConnPageItem, false); connect(this, SIGNAL(currentPageChanged(KPageWidgetItem*,KPageWidgetItem*)), this, SLOT(slot_currentPageChanged(KPageWidgetItem*,KPageWidgetItem*))); //! @todo Change this to message prompt when we move to non-dialog wizard. connect(m_srcConnSel, SIGNAL(connectionSelected(bool)), this, SLOT(slotConnPageItemSelected(bool))); } ImportTableWizard::~ImportTableWizard() { delete m_prjSet; delete m_srcConnSel; } void ImportTableWizard::back() { KAssistantDialog::back(); } void ImportTableWizard::next() { if (currentPage() == m_srcConnPageItem) { if (fileBasedSrcSelected()) { setAppropriate(m_srcDBPageItem, false); } else { setAppropriate(m_srcDBPageItem, true); } } else if (currentPage() == m_alterTablePageItem) { if (m_alterSchemaWidget->nameExists(m_alterSchemaWidget->nameWidget()->nameText())) { KMessageBox::information(this, xi18nc("@info", "%1 name is already used by an existing table. " "Enter different table name to continue.", m_alterSchemaWidget->nameWidget()->nameText()), xi18n("Name Already Used")); return; } } KAssistantDialog::next(); } void ImportTableWizard::accept() { if (m_args) { if (m_finishCheckBox->isChecked()) { m_args->insert("destinationTableName",m_alterSchemaWidget->nameWidget()->nameText()); } else { m_args->remove("destinationTableName"); } } QDialog::accept(); } void ImportTableWizard::reject() { QDialog::reject(); } //=========================================================== // void ImportTableWizard::setupIntroPage() { m_introPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(); m_introPageWidget->setLayout(vbox); KexiUtils::setStandardMarginsAndSpacing(vbox); QLabel *lblIntro = new QLabel(m_introPageWidget); lblIntro->setAlignment(Qt::AlignTop | Qt::AlignLeft); lblIntro->setWordWrap(true); lblIntro->setText( xi18nc("@info", "Table Importing Assistant allows you to import a table from an existing " "database into the current Kexi project." "Click Next button to continue or " "Cancel button to exit this assistant.")); vbox->addWidget(lblIntro); m_introPageItem = new KPageWidgetItem(m_introPageWidget, xi18n("Welcome to the Table Importing Assistant")); addPage(m_introPageItem); } void ImportTableWizard::setupSrcConn() { m_srcConnPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(m_srcConnPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_srcConnSel = new KexiConnectionSelectorWidget(&Kexi::connset(), "kfiledialog:///ProjectMigrationSourceDir", KFileWidget::Opening, m_srcConnPageWidget); m_srcConnSel->hideConnectonIcon(); m_srcConnSel->showSimpleConn(); QSet excludedFilters; //! @todo remove when support for kexi files as source prj is added in migration excludedFilters << KDb::defaultFileBasedDriverMimeType() << "application/x-kexiproject-shortcut" << "application/x-kexi-connectiondata"; m_srcConnSel->fileWidget->setExcludedFilters(excludedFilters); vbox->addWidget(m_srcConnSel); m_srcConnPageItem = new KPageWidgetItem(m_srcConnPageWidget, xi18n("Select Location for Source Database")); addPage(m_srcConnPageItem); } void ImportTableWizard::setupSrcDB() { // arrivesrcdbPage creates widgets on that page m_srcDBPageWidget = new QWidget(this); m_srcDBName = NULL; m_srcDBPageItem = new KPageWidgetItem(m_srcDBPageWidget, xi18n("Select Source Database")); addPage(m_srcDBPageItem); } void ImportTableWizard::setupTableSelectPage() { m_tablesPageWidget = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(m_tablesPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_tableListWidget = new QListWidget(this); m_tableListWidget->setSelectionMode(QAbstractItemView::SingleSelection); connect(m_tableListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotTableListWidgetSelectionChanged())); vbox->addWidget(m_tableListWidget); m_tablesPageItem = new KPageWidgetItem(m_tablesPageWidget, xi18n("Select the Table to Import")); addPage(m_tablesPageItem); } //=========================================================== // void ImportTableWizard::setupImportingPage() { m_importingPageWidget = new QWidget(this); m_importingPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_importingPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_lblImportingTxt = new QLabel(m_importingPageWidget); m_lblImportingTxt->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_lblImportingTxt->setWordWrap(true); m_lblImportingErrTxt = new QLabel(m_importingPageWidget); m_lblImportingErrTxt->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_lblImportingErrTxt->setWordWrap(true); vbox->addWidget(m_lblImportingTxt); vbox->addWidget(m_lblImportingErrTxt); vbox->addStretch(1); QWidget *options_widget = new QWidget(m_importingPageWidget); vbox->addWidget(options_widget); QVBoxLayout *options_vbox = new QVBoxLayout(options_widget); options_vbox->setSpacing(KexiUtils::spacingHint()); m_importOptionsButton = new QPushButton(koIcon("configure"), xi18n("Advanced Options"), options_widget); connect(m_importOptionsButton, SIGNAL(clicked()),this, SLOT(slotOptionsButtonClicked())); options_vbox->addWidget(m_importOptionsButton); options_vbox->addStretch(1); m_importingPageWidget->show(); m_importingPageItem = new KPageWidgetItem(m_importingPageWidget, xi18n("Importing")); addPage(m_importingPageItem); } void ImportTableWizard::setupAlterTablePage() { m_alterTablePageWidget = new QWidget(this); m_alterTablePageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_alterTablePageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_alterSchemaWidget = new KexiMigration::AlterSchemaWidget(this); vbox->addWidget(m_alterSchemaWidget); m_alterTablePageWidget->show(); m_alterTablePageItem = new KPageWidgetItem(m_alterTablePageWidget, xi18n("Alter the Detected Table Design")); addPage(m_alterTablePageItem); } void ImportTableWizard::setupProgressPage() { m_progressPageWidget = new QWidget(this); m_progressPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_progressPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_progressPageWidget->setLayout(vbox); m_progressLbl = new QLabel(m_progressPageWidget); m_progressLbl->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_progressLbl->setWordWrap(true); m_rowsImportedLbl = new QLabel(m_progressPageWidget); m_importingProgressBar = new QProgressBar(m_progressPageWidget); m_importingProgressBar->setMinimum(0); m_importingProgressBar->setMaximum(0); m_importingProgressBar->setValue(0); vbox->addWidget(m_progressLbl); vbox->addWidget(m_rowsImportedLbl); vbox->addWidget(m_importingProgressBar); vbox->addStretch(1); m_progressPageItem = new KPageWidgetItem(m_progressPageWidget, xi18n("Processing Import")); addPage(m_progressPageItem); } void ImportTableWizard::setupFinishPage() { m_finishPageWidget = new QWidget(this); m_finishPageWidget->hide(); QVBoxLayout *vbox = new QVBoxLayout(m_finishPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_finishLbl = new QLabel(m_finishPageWidget); m_finishLbl->setAlignment(Qt::AlignTop | Qt::AlignLeft); m_finishLbl->setWordWrap(true); vbox->addWidget(m_finishLbl); m_finishCheckBox = new QCheckBox(xi18n("Open imported table"), m_finishPageWidget); vbox->addSpacing(KexiUtils::spacingHint()); vbox->addWidget(m_finishCheckBox); vbox->addStretch(1); m_finishPageItem = new KPageWidgetItem(m_finishPageWidget, xi18n("Success")); addPage(m_finishPageItem); } void ImportTableWizard::slot_currentPageChanged(KPageWidgetItem* curPage,KPageWidgetItem* prevPage) { Q_UNUSED(prevPage); if (curPage == m_introPageItem) { } else if (curPage == m_srcConnPageItem) { arriveSrcConnPage(); } else if (curPage == m_srcDBPageItem) { arriveSrcDBPage(); } else if (curPage == m_tablesPageItem) { arriveTableSelectPage(prevPage); } else if (curPage == m_alterTablePageItem) { if (prevPage == m_tablesPageItem) { arriveAlterTablePage(); } } else if (curPage == m_importingPageItem) { arriveImportingPage(); } else if (curPage == m_progressPageItem) { arriveProgressPage(); } else if (curPage == m_finishPageItem) { arriveFinishPage(); } } void ImportTableWizard::arriveSrcConnPage() { } void ImportTableWizard::arriveSrcDBPage() { if (fileBasedSrcSelected()) { //! @todo Back button doesn't work after selecting a file to import } else if (!m_srcDBName) { m_srcDBPageWidget->hide(); qDebug() << "Looks like we need a project selector widget!"; KDbConnectionData* conndata = m_srcConnSel->selectedConnectionData(); if (conndata) { KexiGUIMessageHandler handler; m_prjSet = new KexiProjectSet(&handler); if (!m_prjSet->setConnectionData(conndata)) { handler.showErrorMessage(m_prjSet->result()); delete m_prjSet; m_prjSet = 0; return; } QVBoxLayout *vbox = new QVBoxLayout(m_srcDBPageWidget); KexiUtils::setStandardMarginsAndSpacing(vbox); m_srcDBName = new KexiProjectSelectorWidget(m_srcDBPageWidget, m_prjSet); vbox->addWidget(m_srcDBName); m_srcDBName->label()->setText(xi18n("Select source database you wish to import:")); } m_srcDBPageWidget->show(); } } void ImportTableWizard::arriveTableSelectPage(KPageWidgetItem *prevPage) { if (prevPage == m_alterTablePageItem) { if (m_tableListWidget->count() == 1) { //we was skiping it before back(); } } else { Kexi::ObjectStatus result; KexiUtils::WaitCursor wait; m_tableListWidget->clear(); m_migrateDriver = prepareImport(&result); bool ok = m_migrateDriver; if (ok) { ok = m_migrateDriver->connectSource(&result); } if (ok) { QStringList tableNames; if (m_migrateDriver->tableNames(&tableNames)) { m_tableListWidget->addItems(tableNames); } if (m_tableListWidget->item(0)) { m_tableListWidget->item(0)->setSelected(true); if (m_tableListWidget->count() == 1) { KexiUtils::removeWaitCursor(); next(); } } } KexiUtils::removeWaitCursor(); if (!ok) { QString errMessage =result.message.isEmpty() ? xi18n("Unknown error") : result.message; QString errDescription = result.description.isEmpty() ? errMessage : result.description; KMessageBox::error(this, errMessage, errDescription); setValid(m_tablesPageItem, false); } } } void ImportTableWizard::arriveAlterTablePage() { //! @todo handle errors if (m_tableListWidget->selectedItems().isEmpty()) return; //! @todo (js) support multiple tables? #if 0 foreach(QListWidgetItem *table, m_tableListWidget->selectedItems()) { m_importTableName = table->text(); } #else m_importTableName = m_tableListWidget->selectedItems().first()->text(); #endif QScopedPointer ts(new KDbTableSchema); if (!m_migrateDriver->readTableSchema(m_importTableName, ts.data())) { return; } setValid(m_alterTablePageItem, ts->fieldCount() > 0); if (isValid(m_alterTablePageItem)) { connect(m_alterSchemaWidget->nameWidget(), SIGNAL(textChanged()), this, SLOT(slotNameChanged()), Qt::UniqueConnection); } m_alterSchemaWidget->setTableSchema(ts.take()); if (!readFromTable()) { m_alterSchemaWidget->setTableSchema(nullptr); back(); KMessageBox::information(this, xi18nc("@info", "Could not import table %1. " "Select different table or cancel importing.", m_importTableName)); } } bool ImportTableWizard::readFromTable() { - QScopedPointer tableResult(m_migrateDriver->readFromTable(m_importTableName)); + QSharedPointer tableResult = m_migrateDriver->readFromTable(m_importTableName); KDbTableSchema *newSchema = m_alterSchemaWidget->newSchema(); if (!tableResult || tableResult->lastResult().isError() || tableResult->fieldsCount() != newSchema->fieldCount()) { back(); KMessageBox::information(this, xi18nc("@info", "Could not import table %1. " "Select different table or cancel importing.", m_importTableName)); return false; } QScopedPointer> data(new QList); for (int i = 0; i < RECORDS_FOR_PREVIEW; ++i) { - QScopedPointer record(tableResult->fetchRecordData()); + QSharedPointer record(tableResult->fetchRecordData()); if (!record) { if (tableResult->lastResult().isError()) { return false; } break; } data->append(record.data()); } if (data->isEmpty()) { back(); KMessageBox::information(this, xi18nc("@info", "No data has been found in table %1. " "Select different table or cancel importing.", m_importTableName)); return false; } m_alterSchemaWidget->model()->setRowCount(data->count()); m_alterSchemaWidget->setData(data.take()); return true; } void ImportTableWizard::arriveImportingPage() { m_importingPageWidget->hide(); #if 0 if (checkUserInput()) { //setNextEnabled(m_importingPageWidget, true); user2Button->setEnabled(true); } else { //setNextEnabled(m_importingPageWidget, false); user2Button->setEnabled(false); } #endif QString txt; txt = xi18nc("@info Table import wizard, final message", "All required information has now been gathered. " "Click Next button to start importing table %1." "Depending on size of the table this may take some time.", m_alterSchemaWidget->nameWidget()->nameText()); m_lblImportingTxt->setText(txt); //temp. hack for MS Access driver only //! @todo for other databases we will need KexiMigration::Conenction //! and KexiMigration::Driver classes bool showOptions = false; if (fileBasedSrcSelected()) { Kexi::ObjectStatus result; KexiMigrate* sourceDriver = prepareImport(&result); if (sourceDriver) { showOptions = !result.error() && sourceDriver->propertyValue("source_database_has_nonunicode_encoding").toBool(); sourceDriver->setData(nullptr); } } if (showOptions) m_importOptionsButton->show(); else m_importOptionsButton->hide(); m_importingPageWidget->show(); } void ImportTableWizard::arriveProgressPage() { m_progressLbl->setText(xi18nc("@info", "Please wait while the table is imported.")); backButton()->setEnabled(false); nextButton()->setEnabled(false); connect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &ImportTableWizard::slotCancelClicked); QApplication::setOverrideCursor(Qt::BusyCursor); m_importComplete = doImport(); QApplication::restoreOverrideCursor(); disconnect(button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &ImportTableWizard::slotCancelClicked); next(); } void ImportTableWizard::arriveFinishPage() { if (m_importComplete) { m_finishLbl->setText(xi18nc("@info", "Table %1 has been imported.", m_alterSchemaWidget->nameWidget()->nameText())); } else { m_finishCheckBox->setEnabled(false); m_finishLbl->setText(xi18n("An error occured.")); } m_migrateDriver->disconnectSource(); button(QDialogButtonBox::Cancel)->setEnabled(false); } bool ImportTableWizard::fileBasedSrcSelected() const { return m_srcConnSel->selectedConnectionType() == KexiConnectionSelectorWidget::FileBased; } KexiMigrate* ImportTableWizard::prepareImport(Kexi::ObjectStatus *result) { Q_ASSERT(result); // Find a source (migration) driver name QString sourceDriverId = driverIdForSelectedSource(); if (sourceDriverId.isEmpty()) { result->setStatus(xi18n("No appropriate migration driver found."), m_migrateManager.possibleProblemsMessage()); } // Get a source (migration) driver KexiMigrate* sourceDriver = 0; if (!result->error()) { sourceDriver = m_migrateManager.driver(sourceDriverId); if (!sourceDriver || m_migrateManager.result().isError()) { qDebug() << "Import migrate driver error..."; result->setStatus(m_migrateManager.resultable()); } } // Set up source (migration) data required for connection if (sourceDriver && !result->error()) { #if 0 // Setup progress feedback for the GUI if (sourceDriver->progressSupported()) { m_progressBar->updateGeometry(); disconnect(sourceDriver, SIGNAL(progressPercent(int)), this, SLOT(progressUpdated(int))); connect(sourceDriver, SIGNAL(progressPercent(int)), this, SLOT(progressUpdated(int))); progressUpdated(0); } #endif bool keepData = true; #if 0 if (m_importTypeStructureAndDataCheckBox->isChecked()) { qDebug() << "Structure and data selected"; keepData = true; } else if (m_importTypeStructureOnlyCheckBox->isChecked()) { qDebug() << "structure only selected"; keepData = false; } else { qDebug() << "Neither radio button is selected (not possible?) presume keep data"; keepData = true; } #endif KexiMigration::Data* md = new KexiMigration::Data(); if (fileBasedSrcSelected()) { KDbConnectionData* conn_data = new KDbConnectionData(); conn_data->setDatabaseName(m_srcConnSel->selectedFileName()); md->source = conn_data; md->sourceName.clear(); } else { md->source = m_srcConnSel->selectedConnectionData(); md->sourceName = m_srcDBName->selectedProjectData()->databaseName(); } md->setShouldCopyData(keepData); sourceDriver->setData(md); return sourceDriver; } return 0; } //=========================================================== // QString ImportTableWizard::driverIdForSelectedSource() { if (fileBasedSrcSelected()) { QMimeDatabase db; QMimeType mime = db.mimeTypeForFile(m_srcConnSel->selectedFileName()); if (!mime.isValid() || mime.name() == "application/octet-stream" || mime.name() == "text/plain") { //try by URL: mime = db.mimeTypeForFile(m_srcConnSel->selectedFileName()); } if (!mime.isValid()) { return QString(); } const QStringList ids(m_migrateManager.driverIdsForMimeType(mime.name())); //! @todo do we want to return first migrate driver for the mime type or allow to select it? return ids.isEmpty() ? QString() : ids.first(); } return m_srcConnSel->selectedConnectionData() ? m_srcConnSel->selectedConnectionData()->databaseName() : QString(); } bool ImportTableWizard::doImport() { KexiGUIMessageHandler msg; KexiProject* project = KexiMainWindowIface::global()->project(); if (!project) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No project available.")); return false; } KexiPart::Part *part = Kexi::partManager().partForPluginId("org.kexi-project.table"); if (!part) { msg.showErrorMessage(Kexi::partManager().result()); return false; } KDbTableSchema* newSchema = m_alterSchemaWidget->newSchema(); if (!newSchema) { msg.showErrorMessage(KDbMessageHandler::Error, xi18n("No table was selected to import.")); return false; } newSchema->setName(m_alterSchemaWidget->nameWidget()->nameText()); newSchema->setCaption(m_alterSchemaWidget->nameWidget()->captionText()); KexiPart::Item* partItemForSavedTable = project->createPartItem(part->info(), newSchema->name()); if (!partItemForSavedTable) { msg.showErrorMessage(project->result()); return false; } //Create the table if (!m_connection->createTable(newSchema, true)) { msg.showErrorMessage(KDbMessageHandler::Error, xi18nc("@info", "Unable to create table %1.", newSchema->name())); return false; } m_alterSchemaWidget->takeTableSchema(); //m_connection takes ownership of the KDbTableSchema object //Import the data QApplication::setOverrideCursor(Qt::BusyCursor); QList row; unsigned int fieldCount = newSchema->fieldCount(); m_migrateDriver->moveFirst(); KDbTransactionGuard tg(m_connection); if (m_connection->result().isError()) { QApplication::restoreOverrideCursor(); return false; } do { for (unsigned int i = 0; i < fieldCount; ++i) { if (m_importWasCanceled) { return false; } if (i % 100 == 0) { QApplication::processEvents(); } row.append(m_migrateDriver->value(i)); } m_connection->insertRecord(newSchema, row); row.clear(); } while (m_migrateDriver->moveNext()); if (!tg.commit()) { QApplication::restoreOverrideCursor(); return false; } QApplication::restoreOverrideCursor(); //Done so save part and update gui partItemForSavedTable->setIdentifier(newSchema->id()); project->addStoredItem(part->info(), partItemForSavedTable); return true; } void ImportTableWizard::slotConnPageItemSelected(bool isSelected) { setValid(m_srcConnPageItem, isSelected); } void ImportTableWizard::slotTableListWidgetSelectionChanged() { setValid(m_tablesPageItem, !m_tableListWidget->selectedItems().isEmpty()); } void ImportTableWizard::slotNameChanged() { setValid(m_alterTablePageItem, !m_alterSchemaWidget->nameWidget()->captionText().isEmpty()); } void ImportTableWizard::slotCancelClicked() { m_importWasCanceled = true; } diff --git a/src/migration/keximigrate.cpp b/src/migration/keximigrate.cpp index 1b62dfc3f..9d35490f0 100644 --- a/src/migration/keximigrate.cpp +++ b/src/migration/keximigrate.cpp @@ -1,867 +1,867 @@ /* This file is part of the KDE project Copyright (C) 2004 Adam Pigg Copyright (C) 2004-2016 Jarosław Staniek Copyright (C) 2005 Martin Ellis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "keximigrate.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KexiMigration; class Q_DECL_HIDDEN KexiMigrate::Private { public: Private() : metaData(nullptr) , migrateData(nullptr) , sourceConnection(nullptr) { } ~Private() { qDeleteAll(kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport); kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear(); delete migrateData; } QString couldNotCreateDatabaseErrorMessage() const { return xi18nc("@info", "Could not create database %1.", migrateData->destinationProjectData()->databaseName()); } //! Info about the driver's plugin const KexiMigratePluginMetaData *metaData; //! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! //! Migrate Options KexiMigration::Data* migrateData; /*! Driver properties dictionary (indexed by name), useful for presenting properties to the user. Set available properties here in driver implementation. */ QMap properties; /*! i18n'd captions for properties. You do not need to set predefined properties' caption in driver implementation -it's done automatically. */ QMap propertyCaptions; //! KDb driver. For instance, it is used for escaping identifiers QPointer kexiDBDriver; /* private */ //! Table schemas from source DB QList tableSchemas; QList kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport; KDbConnectionProxy* sourceConnection; //! Size of migration job quint64 progressTotal = 0; //! Amount of migration job complete quint64 progressDone = 0; //! Don't recalculate progress done until this value is reached. quint64 progressNextReport = 0; }; KexiMigrate::KexiMigrate(QObject *parent, const QVariantList&) : QObject(parent) , KDbResultable() , d(new Private) { } //! Used for computing progress: //! let's assume that each table creation costs the same as inserting 20 rows #define NUM_OF_ROWS_PER_CREATE_TABLE 20 //============================================================================= // Migration parameters KexiMigration::Data* KexiMigrate::data() { return d->migrateData; } void KexiMigrate::setData(KexiMigration::Data* migrateData) { if (d->migrateData && d->migrateData != migrateData) { delete d->migrateData; } d->migrateData = migrateData; } //============================================================================= // Destructor KexiMigrate::~KexiMigrate() { disconnectInternal(); delete d; } const KexiMigratePluginMetaData* KexiMigrate::metaData() const { return d->metaData; } void KexiMigrate::setMetaData(const KexiMigratePluginMetaData *metaData) { d->metaData = metaData; //! @todo KEXI3 d->initInternalProperties(); } KDbConnectionProxy* KexiMigrate::sourceConnection() { return d->sourceConnection; } bool KexiMigrate::checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result, bool *acceptingNeeded) { Q_ASSERT(acceptingNeeded); *acceptingNeeded = false; if (result) result->clearStatus(); KDbDriverManager drvManager; KDbDriver *destDriver = drvManager.driver( d->migrateData->destinationProjectData()->connectionData()->driverId()); if (!destDriver) { if (result) { result->setStatus(drvManager.resultable(), d->couldNotCreateDatabaseErrorMessage()); } return false; } // For file-based dest. projects, we've already asked about overwriting // existing project but for server-based projects we need to ask now. if (destDriver->metaData()->isFileBased()) { return true; //nothing to check } QScopedPointer tmpConn( destDriver->createConnection(*d->migrateData->destinationProjectData()->connectionData())); if (!tmpConn || destDriver->result().isError() || !tmpConn->connect()) { m_result = destDriver->result(); return true; } if (tmpConn->databaseExists(d->migrateData->destinationProjectData()->databaseName())) { *acceptingNeeded = true; } tmpConn->disconnect(); return true; } bool KexiMigrate::isSourceAndDestinationDataSourceTheSame() const { KDbConnectionData* sourcedata = d->migrateData->source; KDbConnectionData* destinationdata = d->migrateData->destinationProjectData()->connectionData(); return sourcedata && destinationdata && d->migrateData->sourceName == d->migrateData->destinationProjectData()->databaseName() && // same database name sourcedata->driverId() == destinationdata->driverId()&& // same driver sourcedata->hostName() == destinationdata->hostName() && // same host sourcedata->databaseName() == destinationdata->databaseName(); // same database name/filename } bool KexiMigrate::connectInternal(Kexi::ObjectStatus* result) { Q_ASSERT(!d->sourceConnection); KDbConnection* conn = drv_createConnection(); bool ok = !this->result().isError(); if (ok) { // note: conn == nullptr does not mean failure if (conn) { d->sourceConnection = new KDbConnectionProxy(conn); } ok = drv_connect(); } if (ok) { return true; } delete d->sourceConnection; // should not exist but do it for sanity d->sourceConnection = nullptr; QString message(xi18n("Could not connect to database %1.", d->migrateData->sourceDatabaseInfoString())); qWarning() << message; if (result) { result->setStatus(this, message); } return false; } bool KexiMigrate::drv_connect() { if (!d->sourceConnection) { return false; } if (!d->sourceConnection->drv_connect() || !d->sourceConnection->drv_useDatabase(data()->sourceName)) { m_result = d->sourceConnection->result(); return false; } return true; } bool KexiMigrate::disconnectInternal() { const bool ok = drv_disconnect(); if (!ok) { if (!m_result.isError()) { if (d->sourceConnection) { m_result = d->sourceConnection->result(); } } } delete d->sourceConnection; d->sourceConnection = 0; return ok; } bool KexiMigrate::drv_disconnect() { if (d->sourceConnection) { return d->sourceConnection->disconnect(); } return false; } bool KexiMigrate::importTable(const QString& tableName, KDbConnectionProxy *destConn) { QScopedPointer t(new KDbTableSchema()); KDbEscapedString sqlStatement = KDbEscapedString( "SELECT o_id, o_type, o_name, o_caption, o_desc " "FROM kexi__objects WHERE o_name=%1 AND o_type=%2") .arg(d->sourceConnection->escapeString(tableName)) .arg(int(KDb::TableObjectType)); QScopedPointer record; { - QScopedPointer result(d->sourceConnection->executeSQL(sqlStatement)); + QSharedPointer result = d->sourceConnection->prepareSql(sqlStatement); if (!result) { m_result = d->sourceConnection->result(); return false; } record.reset(result->fetchRecordData()); if (!record) { return !result->lastResult().isError(); } if (!destConn->setupObjectData(*record, t.data())) { m_result = d->sourceConnection->result(); return false; } } sqlStatement = KDbEscapedString("SELECT t_id, f_type, f_name, f_length, f_precision, f_constraints, " "f_options, f_default, f_order, f_caption, f_help" " FROM kexi__fields WHERE t_id=%1 ORDER BY f_order").arg(t->id()); QVector> fieldRecords; { - QScopedPointer fieldsResult(d->sourceConnection->executeSQL(sqlStatement)); + QSharedPointer fieldsResult = d->sourceConnection->prepareSql(sqlStatement); if (!fieldsResult) { m_result = d->sourceConnection->result(); return false; } Q_FOREVER { QScopedPointer fieldsRecord(fieldsResult->fetchRecordData()); if (!fieldsRecord) { if (!fieldsResult->lastResult().isError()) { break; } m_result = fieldsResult->lastResult(); return false; } QScopedPointer f(destConn->setupField(*fieldsRecord)); if (!f) { return false; } QString testName(f->name()); int i = 1; Q_FOREVER { // try to find unique name if (!t->field(testName)) { break; } ++i; testName = f->name() + QString::number(i); } if (testName != f->name()) { f->setName(testName); if (!f->caption().isEmpty()) { f->setCaption(QString::fromLatin1("%1 %2").arg(f->caption()).arg(i)); } } if (!t->addField(f.data())) { return false; } f.take(); fieldRecords.append(fieldsRecord->toList()); } } if (!destConn->drv_createTable(*t)) { return false; } KDbTableSchema *kexi__objectsTable = destConn->tableSchema("kexi__objects"); KDbTableSchema *kexi__fieldsTable = destConn->tableSchema("kexi__fields"); if (!kexi__objectsTable || !kexi__fieldsTable) { return false; } // copy the kexi__objects record if (!destConn->insertRecord(kexi__objectsTable, record->toList())) { return false; } // copy the kexi__fields records for (const QList &fieldRecordData : fieldRecords) { if (!destConn->insertRecord(kexi__fieldsTable, fieldRecordData)) { return false; } } d->kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.append(t.take()); return true; } bool KexiMigrate::performImport(Kexi::ObjectStatus* result) { if (result) result->clearStatus(); // Step 1 - connect qDebug() << "CONNECTING..."; if (!connectInternal(result)) { return false; } // "Real" steps bool ok = performImportInternal(result); if (!disconnectInternal()) { ok = false; } return ok; } bool KexiMigrate::performImportInternal(Kexi::ObjectStatus* result) { // Step 1 - destination driver KDbDriverManager drvManager; KDbDriver *destDriver = drvManager.driver( d->migrateData->destinationProjectData()->connectionData()->driverId()); if (!destDriver) { result->setStatus(drvManager.resultable(), d->couldNotCreateDatabaseErrorMessage()); return false; } // Step 2 - get table names qDebug() << "GETTING TABLENAMES..."; QStringList tables; if (!tableNames(&tables)) { qWarning() << "Couldn't get list of tables"; if (result) result->setStatus( xi18n("Could not get a list of table names for database %1.", d->migrateData->sourceDatabaseInfoString()), QString()); return false; } // Check if there are any tables if (tables.isEmpty()) { qDebug() << "There were no tables to import"; if (result) result->setStatus( xi18n("No tables have been found in database %1.", d->migrateData->sourceDatabaseInfoString()), QString()); return false; } // Step 3 - Read KDb-compatible table schemas tables.sort(); d->tableSchemas.clear(); const bool kexi__objects_exists = tables.contains("kexi__objects"); QStringList kexiDBTables; if (kexi__objects_exists) { tristate res = drv_queryStringListFromSQL( KDbEscapedString("SELECT o_name FROM kexi__objects WHERE o_type=%1") .arg(int(KDb::TableObjectType)), 0, &kexiDBTables, -1); if (res == true) { // Skip KDb-compatible schemas that have no physical tables QMutableListIterator kdbTablesIt(kexiDBTables); while (kdbTablesIt.hasNext()) { if (true != d->sourceConnection->resultExists(KDbEscapedString("SELECT * FROM %1") .arg(sourceConnection()->escapeIdentifier(kdbTablesIt.next())))) { qDebug() << "KDb table does not exist:" << kdbTablesIt.value(); kdbTablesIt.remove(); } } // Separate KDb-compatible tables from the KDb-incompatible tables // so KDb-compatible tables can be later deeply copied without altering their IDs. kexiDBTables.sort(); const QSet kdbTablesSet(kexiDBTables.toSet()); QMutableListIterator tablesIt(tables); while (tablesIt.hasNext()) { if (kdbTablesSet.contains(tablesIt.next())) { tablesIt.remove(); } } //qDebug() << "KDb-compatible tables: " << kexiDBTables; //qDebug() << "non-KDb tables: " << tables; } } // -- read non-KDb-compatible tables schemas and create them in memory QMap nativeNames; foreach(const QString& tableCaption, tables) { if (destDriver->isSystemObjectName(tableCaption) || KDbDriver::isKDbSystemObjectName(tableCaption) // "kexi__objects", etc. // other "kexi__*" tables at KexiProject level, e.g. "kexi__blobs" || tableCaption.startsWith(QLatin1String("kexi__"), Qt::CaseInsensitive)) { continue; } // this is a non-KDb table: generate schema from native data source const QString tableIdentifier(KDb::stringToIdentifier(tableCaption.toLower())); nativeNames.insert(tableIdentifier, tableCaption); QScopedPointer tableSchema(new KDbTableSchema(tableIdentifier)); tableSchema->setCaption(tableCaption); //caption is equal to the original name if (!drv_readTableSchema(tableCaption, tableSchema.data())) { if (result) result->setStatus( xi18nc("@info", "Could not import project from database %1. Error reading table %2.", d->migrateData->sourceDatabaseInfoString(), tableCaption), QString()); return false; } //yeah, got a table //Add it to list of tables which we will create if all goes well d->tableSchemas.append(tableSchema.take()); } // Step 4 - Create a new database as we have all required info KexiProject destProject( *d->migrateData->destinationProjectData(), result ? (KDbMessageHandler*)*result : 0); bool ok = true == destProject.create(true /*forceOverwrite*/) && destProject.dbConnection(); QScopedPointer destConn; if (ok) { destConn.reset(new KDbConnectionProxy(destProject.dbConnection())); destConn->setParentConnectionIsOwned(false); } KDbTransaction trans; if (ok) { trans = destConn->beginTransaction(); if (trans.isNull()) { ok = false; if (result) { result->setStatus(destConn->parentConnection(), d->couldNotCreateDatabaseErrorMessage()); } } } if (ok) { if (drv_progressSupported()) { ok = progressInitialise(); } } if (ok) { // Step 5 - Create the copies of KDb-compatible tables in memory (to maintain the same IDs) // Step 6.1 - Copy kexi__objects NOW because we'll soon create new objects with new IDs // Step 6.2 - Copy kexi__fields d->kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear(); foreach(const QString& tableName, kexiDBTables) { if (!importTable(tableName, destConn.data())) { if (!m_result.isError()) { m_result.setCode(); } m_result.prependMessage( xi18nc("@info", "Could not import table %1.", tableName)); return false; } } } // Step 7 - Create non-KDb-compatible tables: new IDs will be assigned to them if (ok) { foreach(KDbTableSchema* ts, d->tableSchemas) { ok = destConn->createTable(ts); if (!ok) { qWarning() << "Failed to create a table " << ts->name(); qWarning() << destConn->result(); if (result) { result->setStatus(destConn->parentConnection()->result(), nullptr, d->couldNotCreateDatabaseErrorMessage()); } d->tableSchemas.removeAt(d->tableSchemas.indexOf(ts)); break; } updateProgress((qulonglong)NUM_OF_ROWS_PER_CREATE_TABLE); } } if (ok) ok = destConn->commitTransaction(trans); if (ok) { //add KDb-compatible tables to the list, so data will be copied, if needed if (d->migrateData->shouldCopyData()) { foreach(KDbTableSchema* table, d->kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport) { d->tableSchemas.append(table); } } else d->tableSchemas.clear(); } if (ok) { if (destProject.result().isError()) { ok = false; if (result) result->setStatus(destProject.result(), nullptr, xi18n("Could not import project from data source %1.", d->migrateData->sourceDatabaseInfoString())); } } // Step 8 - Copy data if asked to if (ok) { trans = destConn->beginTransaction(); ok = !trans.isNull(); } if (ok) { if (d->migrateData->shouldCopyData()) { //! @todo check detailed "copy forms/blobs/tables" flags here when we add them //! @todo don't copy kexi__objectdata and kexi__userdata for tables that do not exist // Copy data for "kexi__objectdata" as well, if available in the source db if (tables.contains("kexi__objectdata")) d->tableSchemas.append(destConn->tableSchema("kexi__objectdata")); // Copy data for "kexi__blobs" as well, if available in the source db if (tables.contains("kexi__blobs")) d->tableSchemas.append(destConn->tableSchema("kexi__blobs")); } foreach(KDbTableSchema *ts, d->tableSchemas) { if (!ok) break; if ((destConn->driver()->isSystemObjectName(ts->name()) || KDbDriver::isKDbSystemObjectName(ts->name())) //! @todo what if these two tables are not compatible with tables created in destination db //! because newer db format was used? && ts->name() != "kexi__objectdata" //copy this too && ts->name() != "kexi__blobs" //copy this too && ts->name() != "kexi__userdata" //copy this too ) { qDebug() << "Won't copy data to system table" << ts->name(); //! @todo copy kexi__db contents! continue; } QString tsName = nativeNames.value(ts->name()); qDebug() << "Copying data for table: " << tsName; if (tsName.isEmpty()) { tsName = ts->name(); } ok = drv_copyTable(tsName, destConn->parentConnection(), ts); if (!ok) { qWarning() << "Failed to copy table " << tsName; if (result) result->setStatus(destConn->parentConnection()->result(), nullptr, xi18nc("@info", "Could not copy table %1 to destination database.", tsName)); break; } }//for } // Done. if (ok) ok = destConn->commitTransaction(trans); d->kexiDBCompatibleTableSchemasToRemoveFromMemoryAfterImport.clear(); if (ok) { if (destConn) ok = destConn->disconnect(); return ok; } // Finally: error handling if (result && result->error()) result->setStatus(destConn->parentConnection()->result(), nullptr, xi18n("Could not import data from data source %1.", d->migrateData->sourceDatabaseInfoString())); if (destConn) { qWarning() << destConn->result(); destConn->rollbackTransaction(trans); destConn->disconnect(); destConn->dropDatabase(d->migrateData->destinationProjectData()->databaseName()); } return false; } //============================================================================= bool KexiMigrate::performExport(Kexi::ObjectStatus* result) { if (result) result->clearStatus(); //! @todo performExport return false; } //============================================================================= // Progress functions bool KexiMigrate::progressInitialise() { emit progressPercent(0); //! @todo Don't copy table names here QStringList tables; if (!tableNames(&tables)) return false; // 1) Get the number of rows/bytes to import int tableNumber = 1; quint64 sum = 0; foreach(const QString& tableName, tables) { quint64 size; if (drv_getTableSize(tableName, &size)) { qDebug() << "table:" << tableName << "size: " << (ulong)size; sum += size; emit progressPercent(tableNumber * 5 /* 5% */ / tables.count()); tableNumber++; } else { return false; } } qDebug() << "job size:" << sum; d->progressTotal = sum; d->progressTotal += tables.count() * NUM_OF_ROWS_PER_CREATE_TABLE; d->progressTotal = d->progressTotal * 105 / 100; //add 5 percent for above task 1) d->progressNextReport = sum / 100; d->progressDone = d->progressTotal * 5 / 100; //5 perecent already done in task 1) return true; } void KexiMigrate::updateProgress(qulonglong step) { d->progressDone += step; if (d->progressTotal > 0 && d->progressDone >= d->progressNextReport) { int percent = (d->progressDone + 1) * 100 / d->progressTotal; d->progressNextReport = ((percent + 1) * d->progressTotal) / 100; qDebug() << (ulong)d->progressDone << "/" << (ulong)d->progressTotal << " (" << percent << "%) next report at" << (ulong)d->progressNextReport; emit progressPercent(percent); } } //============================================================================= // Prompt the user to choose a field type KDbField::Type KexiMigrate::userType(const QString& fname) { const QStringList typeNames(KDbField::typeNames()); bool ok; const QString res = QInputDialog::getItem(nullptr, xi18nc("@title:window", "Field Type"), xi18nc("@info", "The data type for field %1 could not be determined. " "Please select one of the following data types.", fname), typeNames, 0, false/* !editable */, &ok); if (!ok || res.isEmpty()) //! @todo OK? return KDbField::Text; return KDb::intToFieldType(int(KDbField::FirstType) + typeNames.indexOf(res)); } QString KexiMigrate::drv_escapeIdentifier(const QString& str) const { return d->kexiDBDriver ? d->kexiDBDriver->escapeIdentifier(str) : str; } KDbDriver *KexiMigrate::driver() { return d->kexiDBDriver; } void KexiMigrate::setDriver(KDbDriver *driver) { d->kexiDBDriver = driver; } QVariant KexiMigrate::propertyValue(const QByteArray& propertyName) { return d->properties.value(propertyName.toLower()); } QString KexiMigrate::propertyCaption(const QByteArray& propertyName) const { return d->propertyCaptions.value(propertyName.toLower()); } void KexiMigrate::setPropertyValue(const QByteArray& propertyName, const QVariant& value) { d->properties.insert(propertyName.toLower(), value); } void KexiMigrate::setPropertyCaption(const QByteArray& propertyName, const QString &caption) { d->propertyCaptions.insert(propertyName.toLower(), caption); } QList KexiMigrate::propertyNames() const { QList names = d->properties.keys(); qSort(names); return names; } /* moved to MigrateManagerInternal::driver(): bool KexiMigrate::isValid() { if (KexiMigration::versionMajor() != versionMajor() || KexiMigration::versionMinor() != versionMinor()) { setError(ERR_INCOMPAT_DRIVER_VERSION, xi18n( "Incompatible migration driver's \"%1\" version: found version %2, expected version %3.", objectName(), QString("%1.%2").arg(versionMajor()).arg(versionMinor()), QString("%1.%2").arg(KexiMigration::versionMajor()).arg(KexiMigration::versionMinor())) ); return false; } return true; } */ bool KexiMigrate::drv_queryMaxNumber(const QString& tableName, const QString& columnName, int *result) { QString string; tristate r = drv_querySingleStringFromSQL( KDbEscapedString("SELECT MAX(%1) FROM %2") .arg(drv_escapeIdentifier(columnName)) .arg(drv_escapeIdentifier(tableName)), 0, &string); if (r == false) return false; if (~r) { result = 0; return true; } bool ok; int tmpResult = string.toInt(&ok); if (ok) *result = tmpResult; return ok; } tristate KexiMigrate::drv_querySingleStringFromSQL( const KDbEscapedString& sqlStatement, int columnNumber, QString *string) { QStringList stringList; const tristate res = drv_queryStringListFromSQL(sqlStatement, columnNumber, &stringList, 1); if (true == res) *string = stringList.first(); return res; } bool KexiMigrate::connectSource(Kexi::ObjectStatus* result) { return connectInternal(result); } bool KexiMigrate::disconnectSource() { return disconnectInternal(); } bool KexiMigrate::readTableSchema(const QString& originalName, KDbTableSchema *tableSchema) { return drv_readTableSchema(originalName, tableSchema); } bool KexiMigrate::tableNames(QStringList *tn) { //! @todo Cache list of table names qDebug() << "Reading list of tables..."; tn->clear(); return drv_tableNames(tn); } -KDbSqlResult* KexiMigrate::readFromTable(const QString & tableName) +QSharedPointer KexiMigrate::readFromTable(const QString & tableName) { return drv_readFromTable(tableName); } bool KexiMigrate::moveNext() { return drv_moveNext(); } bool KexiMigrate::movePrevious() { return drv_movePrevious(); } bool KexiMigrate::moveFirst() { return drv_moveFirst(); } bool KexiMigrate::moveLast() { return drv_moveLast(); } QVariant KexiMigrate::value(int i) { return drv_value(i); } //------------------------ KDbVersionInfo KexiMigration::version() { return KDbVersionInfo(KEXI_MIGRATION_VERSION_MAJOR, KEXI_MIGRATION_VERSION_MINOR, 0); } diff --git a/src/migration/keximigrate.h b/src/migration/keximigrate.h index e3a9d7ce0..4b40696da 100644 --- a/src/migration/keximigrate.h +++ b/src/migration/keximigrate.h @@ -1,383 +1,383 @@ /* This file is part of the KDE project Copyright (C) 2004 Adam Pigg Copyright (C) 2004-2016 Jarosław Staniek Copyright (C) 2005 Martin Ellis This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXI_MIGRATE_H #define KEXI_MIGRATE_H #include "keximigratedata.h" #include "KexiVersion.h" #include #include #include #include class KDbConnectionProxy; namespace Kexi { class ObjectStatus; } class KexiMigratePluginMetaData; /*! KexiMigration implementation version. It is altered after every change: - major number is increased after incompatible change ofthe plugin interface/behavior - minor number is increased after minor changes @note Do not use these constants to get library version information in external code. Use KexiMigratePluginMetaData::*version() functions instead. */ #define KEXI_MIGRATION_VERSION_MAJOR KEXI_STABLE_VERSION_MAJOR #define KEXI_MIGRATION_VERSION_MINOR KEXI_STABLE_VERSION_MINOR /*! * \namespace KexiMigration * \brief Framework for importing databases into native KDb databases. */ namespace KexiMigration { //! \return KexiMigration library version info KEXIMIGRATE_EXPORT KDbVersionInfo version(); //! @short A base class for a migrate plugin that imports native databases into Kexi projects /*! A generic API for importing schema and data from an existing database into a new Kexi project. Can be also used for importing native Kexi databases. Basic idea is this: -# User selects an existing DB and new project (file or server based) -# User specifies whether to import structure and data or structure only. -# Import tool connects to db -# Checks if it is already a kexi project (not implemented yet) -# If not, then read structure and construct new project -# Ask user what to do if column type is not supported See kexi/doc/dev/kexi_import.txt for more info. */ class KEXIMIGRATE_EXPORT KexiMigrate : public QObject, public KDbResultable { Q_OBJECT public: virtual ~KexiMigrate(); //! Info about the driver's plugin const KexiMigratePluginMetaData* metaData() const; //! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! KexiMigration::Data* data(); //! @todo Remove this! KexiMigrate should be usable for multiple concurrent migrations! //! Data Setup. Requires two connection objects, a name and a bool //! Ownership of @a migrateData is transferred to the KexiMigrate object. //! Any previous data is removed if it is different than @a migrateData. void setData(KexiMigration::Data* migrateData); /*! Checks whether the destination database exists. For file-based dest. projects, we've already asked about overwriting existing project but for server-based projects it's better to ask user. This method should be called before performImport() or performExport(). \return true if no connection-related errors occurred. \a acceptingNeeded is set to true if destination database exists. In this case you should ask about accepting database overwriting. Used in ImportWizard::import(). */ bool checkIfDestinationDatabaseOverwritingNeedsAccepting(Kexi::ObjectStatus* result, bool* acceptingNeeded); /*! Checks if the source- and the destination databases are identical. \return true if they are identical else false. */ bool isSourceAndDestinationDataSourceTheSame() const; //! Connects, perform an import operation, and disconnects bool performImport(Kexi::ObjectStatus* result = 0); //! Perform an export operation bool performExport(Kexi::ObjectStatus* result = 0); //! Returns true if the migration driver supports progress updates. inline bool progressSupported() { return drv_progressSupported(); } //! @todo This is copied from KDbDriver. One day it will be merged with KDb. //! \return property value for \a propertyName available for this driver. //! If there's no such property defined for driver, Null QVariant value is returned. virtual QVariant propertyValue(const QByteArray& propertyName); //! @todo This is copied from KDbDriver. One day it will be merged with KDb. void setPropertyValue(const QByteArray& propertyName, const QVariant& value); //! @todo This is copied from KDbDriver. One day it will be merged with KDb. //! \return translated property caption for \a propertyName. //! If there's no such property defined for driver, empty string value is returned. QString propertyCaption(const QByteArray& propertyName) const; //! @todo This is copied from KDbDriver. One day it will be merged with KDb. //! Set translated property caption for \a propertyName. void setPropertyCaption(const QByteArray& propertyName, const QString &caption); //! @todo This is copied from KDbDriver. One day it will be merged with KDb. //! \return a list of property names available for this driver. QList propertyNames() const; //! Extension of existing API to provide generic row access to external data for ImportTableWizard. //! @todo refactor bool connectSource(Kexi::ObjectStatus* result); //! Extension of existing API to close connection for ImportTableWizard. //! @todo refactor bool disconnectSource(); //! Get table names in source database (driver specific) bool tableNames(QStringList *tablenames); //! Read schema for a given table (driver specific) bool readTableSchema(const QString& originalName, KDbTableSchema *tableSchema); //! Starts reading data from the source dataset's table - KDbSqlResult* readFromTable(const QString& tableName); + QSharedPointer readFromTable(const QString& tableName); //!Move to the next row bool moveNext(); //!Move to the previous row bool movePrevious(); //!Move to the next row bool moveFirst(); //!Move to the previous row bool moveLast(); //!Read the data at the given row/field QVariant value(int i); Q_SIGNALS: void progressPercent(int percent); protected: //! Used by MigrateManager. explicit KexiMigrate(QObject *parent, const QVariantList &args = QVariantList()); /*! Used by the migration driver manager to set metaData for just loaded driver. */ void setMetaData(const KexiMigratePluginMetaData *metaData); //! Creates connection to source database and connects. //! If it is a database supported by low-level routines of KDb, sourceConnection() will //! be available afterwards. //! If not, connectInternal() can still return true but sourceConnection() will return //! @c nullptr. //! @see drv_createConnection() drv_connect() bool connectInternal(Kexi::ObjectStatus* result); //! Disconnects from source database. If it is a database supported by low-level routines //! of KDb, destroys the connection object pointed by sourceConnection() too. //! @return true on success. //! @see drv_disconnect() connectInternal() bool disconnectInternal(); //! @return connection to source database if it is a database supported by low-level //! routines of KDb. In other cases such as importing from a TSV file, this function //! returns @c nullptr. KDbConnectionProxy* sourceConnection(); //! Migration drivers that use low-level routines KDb to access the source database //! should create and return a driver-specific KDbConnection object that handles //! connection to the source database. //! Migration drivers that use custom data sources (not KDb-compatible) should return //! @c nullptr. //! @note KexiMigrate::m_result should be set to a result of the operation. virtual KDbConnection* drv_createConnection() = 0; //! Connects to source database using (driver specific). //! Default implementation calls sourceConnection()->drv_connect() and if it succeeds, //! it calls sourceConnection()->drv_useDatabase(data()->sourceName), then returns //! the result. If this is enough for connecting for a migration driver, there is no need //! to reimplement drv_connect(). //! If sourceConnection() is @c nullptr (custom types of sources), //! default implementation just returns @c false. In this case drv_connect() should //! be implemented. virtual bool drv_connect(); //! Disconnect from source database (driver specific). //! If the source database is supported by low-level routines KDb, //! KDbConnection::disconnect() is called for this connection. //! For other types of sources @c false is returned so in these cases this method //! should be reimplemented. virtual bool drv_disconnect(); //! Get table names in source database (driver specific) /*! @return List of table names available for this connection. The names are in lower case. The method should return true only if there was no error on getting database names list from the server. */ virtual bool drv_tableNames(QStringList *tablenames) = 0; //! Read schema for a given table (driver specific) virtual bool drv_readTableSchema( const QString& originalName, KDbTableSchema *tableSchema) = 0; /*! Fetches maximum number from table \a tableName, column \a columnName into \a result. On success true is returned. If there is no records in the table, \a result is set to 0 and true is returned. - Note 1: implement only if the database can already contain kexidb__* tables (so e.g. keximdb driver doea not need this). - Note 2: default implementation uses drv_querySingleStringFromSQL() with "SELECT MAX(columName) FROM tableName" statement, assuming SQL-compliant backend. */ virtual bool drv_queryMaxNumber(const QString& tableName, const QString& columnName, int *result); /*! Fetches single string at column \a columnNumber for each record from result obtained by running \a sqlStatement. \a numRecords can be specified to limit number of records read. If \a numRecords is -1, all records are loaded. On success the result is stored in \a stringList and true is returned. \return cancelled if there are no records available. - Note: implement only if the database can already contain kexidb__* tables (so e.g. keximdb driver does not need this). */ //! @todo SQL-dependent! virtual tristate drv_queryStringListFromSQL( const KDbEscapedString& sqlStatement, int columnNumber, QStringList *stringList, int numRecords = -1) { Q_UNUSED(sqlStatement); Q_UNUSED(columnNumber); Q_UNUSED(stringList); Q_UNUSED(numRecords); return cancelled; } /*! Fetches single string at column \a columnNumber from result obtained by running \a sqlStatement. On success the result is stored in \a string and true is returned. \return cancelled if there are no records available. This implementation uses drv_queryStringListFromSQL() with numRecords == 1. */ //! @todo SQL-dependent! virtual tristate drv_querySingleStringFromSQL(const KDbEscapedString& sqlStatement, int columnNumber, QString *string); //! A functor for filtering records //! @see drv_copyTable() class RecordFilter { public: RecordFilter() {} virtual ~RecordFilter() {} - virtual bool operator() (const KDbSqlRecord &record) const = 0; + virtual bool operator() (const QSharedPointer &record) const = 0; virtual bool operator() (const QList &record) const = 0; }; //! Copy a table from source DB to target DB (driver specific) //! - create copies of KDb tables //! - create copies of non-KDb tables virtual bool drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter = nullptr) = 0; virtual bool drv_progressSupported() { return false; } /*! \return the size of a table to be imported, or 0 if not supported Finds the size of the named table, in order to provide feedback on migration progress. The units of the return type are deliberately unspecified. Migration drivers may return the number of records in the table, or the size in bytes, etc. Units should be chosen in order that the driver can return the size in the fastest way possible (e.g. migration from CSV files should use file size to avoid counting the number of rows, and migration from MDB files should return the number of rows as this is stored within the file). Obviously, the driver should use the same units when reporting migration progress. \return size of the specified table */ virtual bool drv_getTableSize(const QString&, quint64*) { return false; } void updateProgress(quint64 step = 1ULL); //! @todo user should be asked ONCE using a convenient wizard's page, not a popup dialog //! Prompt user to select a field type for unrecognized fields KDbField::Type userType(const QString& fname); virtual QString drv_escapeIdentifier(const QString& str) const; //Extended API //! Position the source dataset at the start of a table - virtual KDbSqlResult* drv_readFromTable(const QString & tableName) { + virtual QSharedPointer drv_readFromTable(const QString & tableName) { Q_UNUSED(tableName); - return nullptr; + return QSharedPointer(); } //! Move to the next row virtual bool drv_moveNext() { return false; } //! Move to the previous row virtual bool drv_movePrevious() { return false; } //! Move to the next row virtual bool drv_moveFirst() { return false; } //! Move to the previous row virtual bool drv_moveLast() { return false; } //! Read the data at the given row/field virtual QVariant drv_value(int i) { Q_UNUSED(i); return QVariant(); }; //! @return Database driver for this migration. KDbDriver *driver(); //! Sets database driver for this migration. void setDriver(KDbDriver *driver); private: /*! Estimate size of migration job Calls drv_getTableSize for each table to be copied. \return sum of the size of all tables to be copied. */ bool progressInitialise(); //! Perform an import operation. It is assumed that source connection is established. //! @see performImport() bool performImportInternal(Kexi::ObjectStatus* result); //! Reads schema for table @a tableName from kexi__objects and kexi__fields. //! On success: //! - copies one record from the original kexi__objects table to the destination //! database's table kexi__objects //! - copies all related records from the original kexi__fields table to the destination //! database's table kexi__fields bool importTable(const QString& tableName, KDbConnectionProxy *destConn); class Private; Private * const d; friend class MigrateManager; friend class MigrateManagerInternal; }; } //namespace KexiMigration #endif diff --git a/src/migration/tsv/TsvMigrate.cpp b/src/migration/tsv/TsvMigrate.cpp index 4a274e87b..75dd6aa2c 100644 --- a/src/migration/tsv/TsvMigrate.cpp +++ b/src/migration/tsv/TsvMigrate.cpp @@ -1,267 +1,269 @@ /* This file is part of the KDE project Copyright (C) 2004-2009 Adam Pigg Copyright (C) 2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TsvMigrate.h" #include #include #include #include #include #include #include const int MAX_SAMPLE_TEXT_SIZE = 1024 * 10; // max 10KiB of text to detect encoding using namespace KexiMigration; /* This is the implementation for the TSV file import routines. */ KEXI_PLUGIN_FACTORY(TsvMigrate, "keximigrate_tsv.json") namespace KexiMigration { struct FileInfo { QFile file; QTextCodec *codec; QVector fieldNames; }; } TsvMigrate::TsvMigrate(QObject *parent, const QVariantList& args) : KexiMigrate(parent, args) { } TsvMigrate::~TsvMigrate() { } KDbConnection* TsvMigrate::drv_createConnection() { // nothing to do, just success m_result = KDbResult(); return nullptr; } bool TsvMigrate::drv_connect() { return QDir().exists(data()->source->databaseName()); } bool TsvMigrate::drv_disconnect() { return true; } bool TsvMigrate::drv_tableNames(QStringList *tablenames) { // return base part of filename only so table name will look better tablenames->append(QFileInfo(data()->source->databaseName()).baseName()); return true; } //! @return next line read from the file split by tabs, decoded to unicode and with last \n removed static QVector readLine(FileInfo *info, bool *eof) { QByteArray line = info->file.readLine(); int count = line.length(); if (line.endsWith('\n')) { --count; } if (line.isEmpty()) { *eof = true; return QVector(); } *eof = false; int i = 0; int start = 0; int fields = 0; QVector result(info->fieldNames.isEmpty() ? 10 : info->fieldNames.count()); for (; i < count; ++i) { if (line[i] == '\t') { if (fields >= result.size()) { result.resize(result.size() * 2); } result[fields] = line.mid(start, i - start); ++fields; start = i + 1; } } result[fields] = line.mid(start, i - start); // last value result.resize(fields + 1); return result; } bool TsvMigrate::drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter) { Q_UNUSED(srcTable) FileInfo info; if (!openFile(&info)) { return false; } Q_FOREVER { bool eof; QVector line = readLine(&info, &eof); if (eof) { break; } QList vals; for(int i = 0; i < line.count(); ++i) { vals.append(line.at(i)); } if (recordFilter && !(*recordFilter)(vals)) { continue; } if (!destConn->insertRecord(dstTable, vals)) { return false; } } return true; } bool TsvMigrate::drv_readTableSchema(const QString& originalName, KDbTableSchema *tableSchema) { Q_UNUSED(originalName) FileInfo info; if (!openFile(&info)) { return false; } for (const QString &name : info.fieldNames) { KDbField *f = new KDbField(name, KDbField::Text); if (!tableSchema->addField(f)) { delete f; tableSchema->clear(); return false; } } return true; } class TsvRecord : public KDbSqlRecord { public: inline explicit TsvRecord(const QVector &values, const FileInfo &m_info) : m_values(values), m_info(&m_info) { } inline QString stringValue(int index) Q_DECL_OVERRIDE { return m_info->codec->toUnicode(m_values.value(index)); } inline QByteArray toByteArray(int index) Q_DECL_OVERRIDE { return m_values.value(index); } inline KDbSqlString cstringValue(int index) Q_DECL_OVERRIDE { return KDbSqlString(m_values[index].constData(), m_values[index].length()); } private: const QVector m_values; const FileInfo *m_info; }; class TsvResult : public KDbSqlResult { public: inline explicit TsvResult(FileInfo *info) : m_info(info), m_eof(false) { Q_ASSERT(info); } inline int fieldsCount() Q_DECL_OVERRIDE { return m_info->fieldNames.count(); } //! Not needed for ImportTableWizard inline KDbSqlField *field(int index) Q_DECL_OVERRIDE { Q_UNUSED(index); return nullptr; } //! Not needed for ImportTableWizard inline KDbField* createField(const QString &tableName, int index) Q_DECL_OVERRIDE { Q_UNUSED(tableName); Q_UNUSED(index); return nullptr; } - inline KDbSqlRecord* fetchRecord() Q_DECL_OVERRIDE { + inline QSharedPointer fetchRecord() Q_DECL_OVERRIDE { + QSharedPointer sqlRecord; QVector record = readLine(m_info, &m_eof); - if (m_eof) { - return nullptr; + if (!m_eof) { + sqlRecord.reset(new TsvRecord(record, *m_info)); } - return new TsvRecord(record, *m_info); + return sqlRecord; } inline KDbResult lastResult() Q_DECL_OVERRIDE { return KDbResult(); } inline ~TsvResult() { delete m_info; } private: FileInfo *m_info; bool m_eof; }; -KDbSqlResult* TsvMigrate::drv_readFromTable(const QString &tableName) +QSharedPointer TsvMigrate::drv_readFromTable(const QString &tableName) { Q_UNUSED(tableName) + QSharedPointer sqlResult; QScopedPointer info(new FileInfo); - if (!openFile(info.data())) { - return nullptr; + if (openFile(info.data())) { + sqlResult.reset(new TsvResult(info.take())); } - return new TsvResult(info.take()); + return sqlResult; } bool TsvMigrate::openFile(FileInfo *info) { info->file.setFileName(data()->source->databaseName()); if (!info->file.open(QIODevice::ReadOnly | QIODevice::Text)) { return false; } { const QByteArray sample(info->file.read(MAX_SAMPLE_TEXT_SIZE)); info->codec = QTextCodec::codecForUtfText(sample); } if (!info->file.seek(0)) { info->codec = 0; info->file.close(); return false; } bool eof; QVector record = readLine(info, &eof); info->fieldNames.resize(record.count()); for (int i = 0; i < record.count(); ++i) { info->fieldNames[i] = info->codec->toUnicode(record[i]); } return !eof; } #include "TsvMigrate.moc" diff --git a/src/migration/tsv/TsvMigrate.h b/src/migration/tsv/TsvMigrate.h index cba75ef39..716c64c11 100644 --- a/src/migration/tsv/TsvMigrate.h +++ b/src/migration/tsv/TsvMigrate.h @@ -1,73 +1,73 @@ /* This file is part of the KDE project Copyright (C) 2004-2009 Adam Pigg Copyright (C) 2016 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXITSVMIGRATE_H #define KEXITSVMIGRATE_H #include #include class QTextCodec; namespace KexiMigration { struct FileInfo; //! "Tab Separated Values" document import plugin class TsvMigrate : public KexiMigrate { Q_OBJECT public: explicit TsvMigrate(QObject *parent, const QVariantList &args = QVariantList()); virtual ~TsvMigrate(); protected: //! Connect to source KDbConnection* drv_createConnection() Q_DECL_OVERRIDE; bool drv_connect() Q_DECL_OVERRIDE; //! Disconnect from source virtual bool drv_disconnect(); //! Get table names in source virtual bool drv_tableNames(QStringList *tablenames); bool drv_copyTable(const QString& srcTable, KDbConnection *destConn, KDbTableSchema* dstTable, const RecordFilter *recordFilter = 0) Q_DECL_OVERRIDE; //! Read schema for a given table virtual bool drv_readTableSchema(const QString& originalName, KDbTableSchema *tableSchema); //! Starts reading data from the source dataset's table - KDbSqlResult* drv_readFromTable(const QString & tableName) Q_DECL_OVERRIDE; + QSharedPointer drv_readFromTable(const QString & tableName) Q_DECL_OVERRIDE; private: bool openFile(FileInfo *info); }; } #endif