Commit fe12aa6a4b54e3d204bee3aef3ab03d07dd06b29

Authored by Steven de Ridder
0 parents

Initial commit. dependencies not resolved yet.

.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +build/
  2 +CMakeLists.txt.user
CMakeLists.txt 0 → 100644
  1 +++ a/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +
  3 +# Check to see where cmake is located.
  4 +if( IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/cmake )
  5 + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
  6 +elseif( IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../cmake )
  7 + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake)
  8 +else()
  9 + return()
  10 +endif()
  11 +
  12 +# Check to see if there is versioning information available
  13 +if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake)
  14 + LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/osdev_versioning/cmake)
  15 + include(osdevversion)
  16 +endif()
  17 +
  18 +include(projectheader)
  19 +project_header(osdev_dbconnector)
  20 +
  21 +add_subdirectory(src)
  22 +add_subdirectory(tests)
  23 +
  24 +# include(packaging)
  25 +# package_component()
README.md 0 → 100644
  1 +++ a/README.md
src/CMakeLists.txt 0 → 100644
  1 +++ a/src/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +include(projectheader)
  3 +project_header(dbconnector)
  4 +
  5 +find_package( Qt5Core REQUIRED )
  6 +find_package( Qt5Sql REQUIRED )
  7 +
  8 +include_directories( SYSTEM
  9 + ${Qt5Core_INCLUDE_DIRS}
  10 + ${Qt5Sql_INCLUDE_DIRS}
  11 +)
  12 +
  13 +include(compiler)
  14 +
  15 +include_directories(
  16 + ${CMAKE_CURRENT_SOURCE_DIR}/../pugixml
  17 + ${CMAKE_CURRENT_SOURCE_DIR}/../logutils
  18 + ${CMAKE_CURRENT_SOURCE_DIR}/../config
  19 + ${CMAKE_CURRENT_SOURCE_DIR}/../dcxml
  20 + ${CMAKE_CURRENT_SOURCE_DIR}/../global
  21 +)
  22 +
  23 +set(SRC_LIST
  24 + ${CMAKE_CURRENT_SOURCE_DIR}/dbconnector.cpp
  25 + ${CMAKE_CURRENT_SOURCE_DIR}/dbrelation.cpp
  26 + ${CMAKE_CURRENT_SOURCE_DIR}/dbconnectionwatchdog.cpp
  27 +)
  28 +
  29 +include(qtmoc)
  30 +create_mocs( SRC_LIST MOC_LIST
  31 + ${CMAKE_CURRENT_SOURCE_DIR}/dbconnector.h
  32 + ${CMAKE_CURRENT_SOURCE_DIR}/dbrelation.h
  33 + ${CMAKE_CURRENT_SOURCE_DIR}/dbconnectionwatchdog.h
  34 +)
  35 +
  36 +set_source_files_properties(
  37 + ${MOC_LIST}
  38 + PROPERTIES
  39 + COMPILE_FLAGS -Wno-undefined-reinterpret-cast
  40 +)
  41 +
  42 +link_directories(
  43 + ${CMAKE_BINARY_DIR}/lib
  44 +)
  45 +
  46 +include(library)
  47 +add_libraries(
  48 + ${Qt5Core_LIBRARIES}
  49 + ${Qt5Sql_LIBRARIES}
  50 + logutils
  51 + global
  52 + pugixml
  53 +)
  54 +
  55 +include(installation)
  56 +install_component()
src/dbconnectionwatchdog.cpp 0 → 100644
  1 +++ a/src/dbconnectionwatchdog.cpp
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +#include "dbconnectionwatchdog.h"
  24 +#include "log.h"
  25 +
  26 +#include <QSqlDatabase>
  27 +#include <QSqlError>
  28 +
  29 +using namespace osdev::components;
  30 +
  31 +DbConnectionWatchDog::DbConnectionWatchDog( int check_interval, QObject *_parent )
  32 + : QObject( _parent )
  33 + , m_pTimer( new QTimer( this ) )
  34 + , m_check_interval( check_interval )
  35 + , m_original_interval( check_interval )
  36 + , m_dbc_valid( true )
  37 +{
  38 +
  39 + #if( QT_VERSION >= QT_VERSION_CHECK( 5, 7, 0) )
  40 + {
  41 + QObject::connect( m_pTimer.data(), &QTimer::timeout, this, &DbConnectionWatchDog::slotStartConnectionCheck );
  42 + }
  43 + #else
  44 + {
  45 + QObject::connect( m_pTimer.data(), SIGNAL( timeout() ), this, SLOT( slotStartConnectionCheck() ) );
  46 + }
  47 + #endif
  48 +
  49 +}
  50 +
  51 +void DbConnectionWatchDog::slotStartConnectionCheck()
  52 +{
  53 + LogDebug( "[DbConnectionWatchDog::slotStartConnectionCheck]", QString( "Checking Database Connection...." ) );
  54 + // Get all the connections from QSqlDatabase
  55 + QStringList connection_names = QSqlDatabase::connectionNames();
  56 + for( QString &conn_name : connection_names )
  57 + {
  58 + QSqlDatabase dbConn = QSqlDatabase::database( conn_name, false );
  59 +
  60 +
  61 + // Check to see if we can execute a query. Not all databases drivers report a database as "open"
  62 + // without actually accessing. It all depends on the Operating System and the connectiontype.
  63 + if( 0 == dbConn.tables().count() )
  64 + {
  65 + // Check the error cause we didn't got any tablenames.
  66 + switch( dbConn.lastError().type() )
  67 + {
  68 + case QSqlError::NoError:
  69 + LogWarning( "[DbConnectionWatchDog::slotStartConnectionCheck]", QString( "No Error or database unavailable on connection %1" ).arg( conn_name ) );
  70 + dbConn.open();
  71 + // Set the check interval to 1 second until the connection is back on.
  72 + m_check_interval = 1;
  73 + break;
  74 +
  75 + case QSqlError::ConnectionError:
  76 + LogWarning( "[DbConnectionWatchDog::slotStartConnectionCheck]", QString( "Connection Error on connection %1" ).arg( conn_name ) );
  77 + dbConn.open();
  78 + // Set the check interval to 1 second until the connection is back on.
  79 + m_check_interval = 1;
  80 + break;
  81 + case QSqlError::UnknownError:
  82 + LogWarning( "[DbConnectionWatchDog::slotStartConnectionCheck]", QString( "Unknown Error on connection %1" ).arg( conn_name ) );
  83 + // Set the check interval to 1 second until the connection is back on.
  84 + m_check_interval = 1;
  85 + break;
  86 +
  87 + // The following Errors are not connection related but need to be here to satisfy the compiler.
  88 + case QSqlError::StatementError:
  89 + case QSqlError::TransactionError:
  90 + break;
  91 + default:
  92 + LogInfo( "[DbConnectionWatchDog::slotStartConnectionCheck]", QString( "Database connection Healthy...." ) )
  93 + }
  94 +
  95 + if( m_dbc_valid )
  96 + {
  97 + m_dbc_valid = false;
  98 + }
  99 +
  100 + emit signalDbConnected( conn_name, false );
  101 + }
  102 + else
  103 + {
  104 + m_check_interval = m_original_interval;
  105 + if( !m_dbc_valid )
  106 + {
  107 + LogInfo( "[DbConnectionWatchDog::slotStartConnectionCheck]", "Database connection restored.." );
  108 + m_dbc_valid = true;
  109 + emit signalDbConnected( conn_name, m_dbc_valid );
  110 + }
  111 + }
  112 + }
  113 +
  114 + // Restart the watchDog
  115 + this->start();
  116 +}
  117 +
  118 +void DbConnectionWatchDog::start()
  119 +{
  120 + m_pTimer->setInterval( m_check_interval * 1000 );
  121 + m_pTimer->setSingleShot( true );
  122 +
  123 + m_pTimer->start();
  124 +}
src/dbconnectionwatchdog.h 0 → 100644
  1 +++ a/src/dbconnectionwatchdog.h
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +
  24 +#ifndef OSDEV_COMPONENTS_DBCONNECTIONWATCHDOG_H
  25 +#define OSDEV_COMPONENTS_DBCONNECTIONWATCHDOG_H
  26 +
  27 +#include <QObject>
  28 +#include <QTimer>
  29 +#include <QPointer>
  30 +
  31 +namespace osdev {
  32 +namespace components {
  33 +/*!
  34 + * \brief The DbConnectionWatchDog class checks on intervals if all registered database
  35 + * connections are still active. By using the simplest query form like fetching
  36 + * all tablenames on the connectionlevel, we trigger an error.
  37 + * Connection is checked every (check_interval) seconds. If an error is detected,
  38 + * check_interval is set to 1 seconds, reverting back to <check_interval> if the
  39 + * connection checks out again.
  40 + */
  41 +class DbConnectionWatchDog : public QObject
  42 +{
  43 + Q_OBJECT
  44 +
  45 +public:
  46 + /*!
  47 + * \brief The constructor
  48 + * \param check_interval in seconds.
  49 + * \param parent - The object that owns this connection guard.
  50 + */
  51 + explicit DbConnectionWatchDog( int check_interval = 5, QObject *_parent = 0 );
  52 +
  53 + ~DbConnectionWatchDog() {}
  54 +
  55 + /// Deleted Copy Constructor
  56 + DbConnectionWatchDog( const DbConnectionWatchDog& source ) = delete;
  57 + /// Deleted Assignment Operator
  58 + DbConnectionWatchDog& operator=( const DbConnectionWatchDog& ) = delete;
  59 + /// Deleted Move Constructor
  60 + DbConnectionWatchDog( DbConnectionWatchDog&& ) = delete;
  61 + /// Deleted Move Operator
  62 + DbConnectionWatchDog& operator=( DbConnectionWatchDog&& ) = delete;
  63 +
  64 + void start();
  65 +
  66 +private:
  67 + QPointer<QTimer> m_pTimer; ///< The timer object this guard is based on.
  68 + int m_check_interval; ///< The current check_interval the connections are checked.
  69 + int m_original_interval; ///< The check_interval the connections are checked if present.
  70 + bool m_dbc_valid; ///< Private member reflecting the state. True for no problems, false for problems.
  71 +
  72 +signals:
  73 + void signalDbConnected( const QString& db_name, bool is_open );
  74 +
  75 +private slots:
  76 + /*!
  77 + * \brief Slot being called if the timer reaches its timeout value, starting the connectioncheck.
  78 + */
  79 + void slotStartConnectionCheck();
  80 +};
  81 +
  82 +} /* End namespace components */
  83 +} /* End namespace osdev */
  84 +
  85 +#endif /* OSDEV_COMPONENTS_DBCONNECTIONWATCHDOG_H */
src/dbconnector.cpp 0 → 100644
  1 +++ a/src/dbconnector.cpp
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +#include "dbconnector.h"
  24 +
  25 +#include <QtDebug>
  26 +#include <QSqlError>
  27 +#include <QSqlDriver>
  28 +#include <QSqlField>
  29 +#include <QSqlQuery>
  30 +#include <QSqlRecord>
  31 +#include <QSqlIndex>
  32 +#include <QVariant>
  33 +#include <QCoreApplication>
  34 +
  35 +#include "log.h"
  36 +#include "dcxmlconfig.h"
  37 +#include "dbrelation.h"
  38 +#include "conversionutils.h"
  39 +
  40 +using namespace osdev::components;
  41 +
  42 +DbConnector::DbConnector()
  43 + : m_pWatchDog( QPointer<DbConnectionWatchDog>() )
  44 + , m_dataBase()
  45 + , m_sUserName()
  46 + , m_sPassword()
  47 + , m_sDatabaseName()
  48 + , m_sHostName()
  49 + , m_sDbType()
  50 + , m_sDbIdentifier()
  51 + , m_bDbOpen( false )
  52 + , m_qhRelations()
  53 + , m_qhTableInfo()
  54 +{
  55 + this->constructQueryHash();
  56 +}
  57 +
  58 +DbConnector::DbConnector( const QString& sDbIdentifier )
  59 + : m_pWatchDog( QPointer<DbConnectionWatchDog>() )
  60 + , m_dataBase()
  61 + , m_sUserName()
  62 + , m_sPassword()
  63 + , m_sDatabaseName()
  64 + , m_sHostName()
  65 + , m_sDbType()
  66 + , m_sDbIdentifier( sDbIdentifier )
  67 + , m_bDbOpen( false )
  68 + , m_qhRelations()
  69 + , m_qhTableInfo()
  70 +{
  71 + this->constructQueryHash();
  72 +}
  73 +
  74 +DbConnector::DbConnector( const QString& sUserName,
  75 + const QString& sPassword,
  76 + const QString& sDatabaseName,
  77 + const QString& sHostName,
  78 + const QString& sDbType,
  79 + const QString& sDbIdentifier )
  80 + : m_pWatchDog( QPointer<DbConnectionWatchDog>() )
  81 + , m_dataBase()
  82 + , m_sUserName( sUserName )
  83 + , m_sPassword( sPassword )
  84 + , m_sDatabaseName( sDatabaseName )
  85 + , m_sHostName( sHostName )
  86 + , m_sDbType( sDbType )
  87 + , m_sDbIdentifier( sDbIdentifier )
  88 + , m_bDbOpen( false )
  89 + , m_qhRelations()
  90 + , m_qhTableInfo()
  91 +{
  92 + this->constructQueryHash();
  93 +}
  94 +
  95 +DbConnector::DbConnector(const QHash<QString, QString>& _qhCredentials )
  96 + : DbConnector( _qhCredentials.value( "username" ),
  97 + _qhCredentials.value( "password" ),
  98 + _qhCredentials.value( "dbname" ),
  99 + _qhCredentials.value( "hostname" ),
  100 + _qhCredentials.value( "dbtype" ),
  101 + _qhCredentials.value( "identifier" ) )
  102 +{
  103 + this->constructQueryHash();
  104 +}
  105 +
  106 +DbConnector::DbConnector( const DbConnector& source )
  107 + : QObject( source.parent() )
  108 + , m_pWatchDog( QPointer<DbConnectionWatchDog>() )
  109 + , m_dataBase( source.getDatabase() )
  110 + , m_sUserName( source.getUsername() )
  111 + , m_sPassword( source.getPassWord() )
  112 + , m_sDatabaseName( source.getDatabaseName() )
  113 + , m_sHostName( source.getHostName() )
  114 + , m_sDbType( source.getDbType() )
  115 + , m_sDbIdentifier( source.getDbIdentifier() )
  116 + , m_bDbOpen( source.isOpen() )
  117 + , m_qhRelations()
  118 + , m_qhTableInfo()
  119 +{
  120 + this->constructQueryHash();
  121 +}
  122 +
  123 +DbConnector::~DbConnector()
  124 +{
  125 +}
  126 +
  127 +/****************************************************************************
  128 + * C o n n e c t o r a n d G e t / S e t m e t h o d s
  129 + ****************************************************************************/
  130 +bool DbConnector::connectDatabase()
  131 +{
  132 + m_dataBase = QSqlDatabase::addDatabase( m_sDbType, m_sDbIdentifier );
  133 + m_dataBase.setDatabaseName( m_sDatabaseName );
  134 + m_dataBase.setHostName( m_sHostName );
  135 + m_dataBase.setPassword( m_sPassword );
  136 + m_dataBase.setUserName( m_sUserName );
  137 +
  138 + m_bDbOpen = m_dataBase.open();
  139 +
  140 + if( !m_bDbOpen )
  141 + {
  142 + LogError( "[dbConnector::ConnectDatabase]", QString( "There was an error opening database %1 [dbConnector::ConnectDatabase] %2" )
  143 + .arg( m_sDatabaseName).arg(m_dataBase.lastError().text() ) );
  144 +
  145 + LogError( "[dbConnector::ConnectDatabase]", QString( "No use in continuing without a database. Ending %1" ).arg( QCoreApplication::applicationName() ) );
  146 + }
  147 + else
  148 + {
  149 + LogInfo( "[dbConnector::ConnectDatabase]", "Database successfull connected" );
  150 + }
  151 +
  152 + return m_bDbOpen;
  153 +}
  154 +
  155 +void DbConnector::setUserName( const QString& sUserName )
  156 +{
  157 + m_sUserName = sUserName;
  158 +}
  159 +
  160 +QString DbConnector::getUsername() const
  161 +{
  162 + return m_sUserName;
  163 +}
  164 +
  165 +void DbConnector::setPassword(const QString& sPassword )
  166 +{
  167 + m_sPassword = sPassword;
  168 +}
  169 +
  170 +QString DbConnector::getPassWord() const
  171 +{
  172 + return m_sPassword;
  173 +}
  174 +
  175 +void DbConnector::setDatabaseName( const QString& sDatabaseName )
  176 +{
  177 + m_sDatabaseName = sDatabaseName;
  178 +}
  179 +
  180 +QString DbConnector::getDatabaseName() const
  181 +{
  182 + return m_sDatabaseName;
  183 +}
  184 +
  185 +void DbConnector::setHostName(const QString& sHostname )
  186 +{
  187 + m_sHostName = sHostname;
  188 +}
  189 +
  190 +QString DbConnector::getHostName() const
  191 +{
  192 + return m_sHostName;
  193 +}
  194 +
  195 +void DbConnector::setDbType( const QString& sDbType )
  196 +{
  197 + m_sDbType = sDbType;
  198 +}
  199 +
  200 +QString DbConnector::getDbType() const
  201 +{
  202 + return m_sDbType;
  203 +}
  204 +
  205 +void DbConnector::setDbIdentifier(const QString& sDbIdentifier )
  206 +{
  207 + m_sDbIdentifier = sDbIdentifier;
  208 +}
  209 +
  210 +QString DbConnector::getDbIdentifier() const
  211 +{
  212 + return m_sDbIdentifier;
  213 +}
  214 +
  215 +QString DbConnector::getLastError() const
  216 +{
  217 + return m_dataBase.lastError().text();
  218 +}
  219 +
  220 +void DbConnector::setCredentials(const QHash<DCXmlConfig::eDbSettings, QString> &_qhCredentials )
  221 +{
  222 + m_sUserName = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbUsername );
  223 + m_sPassword = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbPassword );
  224 + m_sDatabaseName = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbName );
  225 + m_sHostName = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbHostName );
  226 + m_sDbType = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbType );
  227 + m_sDbIdentifier = _qhCredentials.value( DCXmlConfig::eDbSettings::eDbIdentifier );
  228 +}
  229 +
  230 +QSqlDatabase DbConnector::getDatabase() const
  231 +{
  232 + return selectDatabase();
  233 +}
  234 +
  235 +bool DbConnector::supportTransactions() const
  236 +{
  237 + return this->selectDatabase().driver()->hasFeature( QSqlDriver::Transactions );
  238 +}
  239 +
  240 +bool DbConnector::transactionBegin()
  241 +{
  242 + return this->selectDatabase().transaction();
  243 +}
  244 +
  245 +bool DbConnector::transactionCommit()
  246 +{
  247 + return this->selectDatabase().commit();
  248 +}
  249 +
  250 +bool DbConnector::transactionRollback()
  251 +{
  252 + return this->selectDatabase().rollback();
  253 +}
  254 +
  255 +/****************************************************************************
  256 + * C R U D F u n c t i o n a l i t y
  257 + ****************************************************************************/
  258 +bool DbConnector::createRecord( const QSqlRecord& record, const QString& sTable )
  259 +{
  260 + // Build the fieldsList and values list, based on the QSqlRecord.
  261 + // When done, call createRecord( sField, sValues, sTable );
  262 + QStringList lstFields;
  263 + QStringList lstValues;
  264 +
  265 + for( int index = 0; index < record.count(); index++ )
  266 + {
  267 + lstFields.append( this->quoteFieldName( record.fieldName( index ) ) );
  268 + lstValues.append( this->quoteFieldValue( record.field(index).value() ) );
  269 + }
  270 +
  271 + if( lstFields.isEmpty() || lstValues.isEmpty() )
  272 + {
  273 + return false;
  274 + }
  275 +
  276 + return this->createRecord( lstFields.join( "," ), lstValues.join( "," ), this->quoteTableName( sTable ) );
  277 +}
  278 +
  279 +bool DbConnector::createRecord(const QString& sFields,
  280 + const QString& sValues,
  281 + const QString& sTable )
  282 +{
  283 + bool bResult = false;
  284 +
  285 + QSqlDatabase mDb = this->selectDatabase();
  286 + if( mDb.isValid() && mDb.isOpen() )
  287 + {
  288 + QSqlQuery oQuery( mDb );
  289 + bResult = oQuery.exec( "INSERT INTO " + sTable
  290 + + " ( " + sFields + " ) "
  291 + + "VALUES ( " + sValues + " ) " );
  292 + if( !bResult )
  293 + {
  294 + LogError("dbConnector::createRecord",
  295 + QString( "There was a problem adding a record : %1 [%2]" )
  296 + .arg( oQuery.lastQuery() )
  297 + .arg( oQuery.lastError().text() ) );
  298 + }
  299 + else
  300 + {
  301 + LogDebug("dbConnector::createRecord", "Record saved to database.");
  302 + }
  303 + }
  304 + else
  305 + {
  306 + LogError("dbConnector::createRecord",
  307 + "Database is closed. Please connect first");
  308 + }
  309 +
  310 + return true; // @todo : For now..
  311 +}
  312 +
  313 +QSqlQuery* DbConnector::readRecord(const QString& sQuery )
  314 +{
  315 + QSqlQuery *pQuery = nullptr;
  316 +
  317 + QSqlDatabase mDb = this->selectDatabase();
  318 + if( mDb.isValid() && mDb.isOpen() )
  319 + {
  320 + pQuery = new QSqlQuery( sQuery, mDb );
  321 + if( pQuery->exec() )
  322 + {
  323 + LogError("dbConnector::readRecord",
  324 + "There was a problem reading a record : "
  325 + + pQuery->lastError().text());
  326 + }
  327 + else
  328 + {
  329 + LogInfo("dbConnector::readRecord", "Record updated.");
  330 + }
  331 + }
  332 + else
  333 + {
  334 + LogError("dbConnector::readRecord",
  335 + "Database is closed. Please connect first");
  336 + }
  337 +
  338 + return pQuery;
  339 +}
  340 +
  341 +QSqlQuery* DbConnector::readTableData( const QString& sTable ) const
  342 +{
  343 + QSqlQuery *pQuery = nullptr;
  344 +
  345 + QSqlDatabase mDb = this->selectDatabase();
  346 + if( mDb.isValid() && mDb.isOpen() )
  347 + {
  348 + pQuery = new QSqlQuery( mDb );
  349 + QString sQuery = QString( "SELECT * FROM %1" ).arg( this->quoteTableName( sTable) );
  350 +
  351 + if ( !pQuery->exec( sQuery ) )
  352 + {
  353 + LogError("dbConnector::readTableData",
  354 + "There was an error retrieving the tableData");
  355 + LogError("dbConnector::readTableData",
  356 + pQuery->lastError().text());
  357 + LogError("dbConnector::readTableData",
  358 + pQuery->lastQuery());
  359 + }
  360 + }
  361 +
  362 + return pQuery;
  363 +}
  364 +
  365 +bool DbConnector::updateRecord(const QString& sIdNumber,
  366 + const QString& sIdField,
  367 + const QString& sValue,
  368 + const QString& sFieldName,
  369 + const QString& sTable )
  370 +{
  371 + bool bResult = false;
  372 +
  373 + QSqlDatabase mDb = this->selectDatabase();
  374 + if( mDb.isValid() && mDb.isOpen() )
  375 + {
  376 + QSqlQuery oQuery( mDb );
  377 + bResult = oQuery.exec( "UPDATE " + this->quoteTableName(sTable)
  378 + + " SET " + sFieldName + " = " + sValue
  379 + + " WHERE " + sIdField + " = " + sIdNumber );
  380 + if( !bResult )
  381 + {
  382 + LogError("dbConnector::updateRecord",
  383 + "There was a problem updating a record : "
  384 + + oQuery.lastError().text());
  385 + }
  386 + else
  387 + {
  388 + LogInfo("dbConnector::updateRecord", "Record updated.");
  389 + }
  390 + }
  391 + else
  392 + {
  393 + LogError("dbConnector::updateRecord",
  394 + "Database is closed. Please connect first");
  395 + }
  396 +
  397 + return bResult;
  398 +}
  399 +
  400 +bool DbConnector::deleteRecord( const QString& sIdNumber,
  401 + const QString& sIdField,
  402 + const QString& sTable )
  403 +{
  404 + bool bResult = false;
  405 +
  406 + QSqlDatabase mDb = this->selectDatabase();
  407 + if( mDb.isValid() && mDb.isOpen() )
  408 + {
  409 + QSqlQuery oQuery( mDb );
  410 + bResult = oQuery.exec( "DELETE FROM " + this->quoteTableName(sTable)
  411 + + " WHERE ( " + sIdField + ")"
  412 + + " = ( " + sIdNumber + " ) " );
  413 + if( !bResult )
  414 + {
  415 + LogError("dbConnector::deleteRecord",
  416 + "There was a problem deleting a record : "
  417 + + oQuery.lastError().text());
  418 + }
  419 + else
  420 + {
  421 + LogInfo("dbConnector::deleteRecord",
  422 + "Record deleted from database.");
  423 + }
  424 + }
  425 + else
  426 + {
  427 + LogError("dbConnector::deleteRecord",
  428 + "Database is closed. Please connect first");
  429 + }
  430 +
  431 + return bResult;
  432 +}
  433 +
  434 +/****************************************************************************
  435 + * H E L P E R M E T H O D S
  436 + ****************************************************************************/
  437 +bool DbConnector::clearTable( const QString& sTableName )
  438 +{
  439 + bool bResult = false;
  440 +
  441 + QSqlDatabase mDb = this->selectDatabase();
  442 + if( mDb.isValid() && mDb.isOpen() )
  443 + {
  444 + QSqlQuery oQuery( mDb );
  445 + bResult = oQuery.exec( "DELETE FROM " + this->quoteTableName(sTableName) );
  446 + if( !bResult )
  447 + {
  448 + LogError("dbConnector::clearTable",
  449 + "There was a problem deleting the records : "
  450 + + oQuery.lastError().text());
  451 + }
  452 + else
  453 + {
  454 + LogInfo("dbConnector::clearTable", "Table " + sTableName + " Emptied.");
  455 + bResult = true;
  456 + }
  457 + }
  458 + else
  459 + {
  460 + LogError("dbConnector::clearTable",
  461 + "Database is closed. Please connect first");
  462 + }
  463 +
  464 + return bResult;
  465 +}
  466 +
  467 +int DbConnector::getNumRecords( const QString& sTable ) const
  468 +{
  469 + int nResult = -1;
  470 +
  471 + QSqlDatabase mDb = this->selectDatabase();
  472 + if( mDb.isValid() && mDb.isOpen() )
  473 + {
  474 + QSqlQuery oQuery( mDb );
  475 + if( oQuery.exec( "SELECT COUNT(*) as NumRecords FROM " + this->quoteTableName(sTable) ) )
  476 + {
  477 + nResult = 0;
  478 + // Retrieve the Numbers of Records
  479 + while ( oQuery.next() )
  480 + {
  481 + nResult += oQuery.record().value( "NumRecords" ).toInt();
  482 + }
  483 + }
  484 + else
  485 + {
  486 + LogError("dbConnector::getNumRecords",
  487 + "There was a problem counting the records : "
  488 + + oQuery.lastError().text());
  489 + }
  490 + }
  491 + else
  492 + {
  493 + LogError("dbConnector::getNumRecords", "Database is closed. Please connect first");
  494 + }
  495 +
  496 + return nResult;
  497 +}
  498 +
  499 +bool DbConnector::recordsExist( const QString& sTable, const QString& sField, const QList<QVariant>& slFieldValues) const
  500 +{
  501 + static const QString countQuery("SELECT COUNT(DISTINCT %2) as NumRecords FROM %1 WHERE %2 IN ( %3 )");
  502 + if (slFieldValues.isEmpty())
  503 + {
  504 + return true;
  505 + }
  506 +
  507 + QStringList valueList;
  508 + for ( const auto& v : slFieldValues)
  509 + {
  510 + valueList.append(this->toSqlValueString(v));
  511 + }
  512 + QStringList uniqueValueList = valueList.toSet().toList();
  513 + auto queryResult = this->executeQuery(countQuery
  514 + .arg(this->quoteTableName(sTable))
  515 + .arg(this->quoteFieldName(sField))
  516 + .arg(uniqueValueList.join(",")));
  517 + if (!queryResult)
  518 + {
  519 + return false;
  520 + }
  521 +
  522 + int nResult = 0;
  523 + while ( queryResult->next() )
  524 + {
  525 + nResult += queryResult->record().value( "NumRecords" ).toInt();
  526 + }
  527 +
  528 + return ( uniqueValueList.size() == nResult );
  529 +}
  530 +
  531 +QStringList DbConnector::getTableNames() const
  532 +{
  533 + QStringList lstTableNames;
  534 +
  535 + QSqlDatabase mDb = this->selectDatabase();
  536 + if( mDb.isValid() && mDb.isOpen() )
  537 + {
  538 + lstTableNames = mDb.tables();
  539 + }
  540 +
  541 + return lstTableNames;
  542 +}
  543 +
  544 +QStringList DbConnector::getFieldNames( const QString& tableName ) const
  545 +{
  546 + return QStringList( this->getFieldNamesAndTypes( tableName ).keys() );
  547 +}
  548 +
  549 +QHash<QString, QVariant::Type> DbConnector::getFieldNamesAndTypes(
  550 + const QString& tableName ) const
  551 +{
  552 + QHash<QString, QVariant::Type> qhResult;
  553 + QString _table;
  554 +
  555 + // Check if there is a 'dot' in the tablename. A Scheme is present which should be removed.
  556 + if( tableName.contains( "." ) )
  557 + {
  558 + _table = stripSchemaFromTable( tableName );
  559 + }
  560 + else
  561 + {
  562 + _table = tableName;
  563 + }
  564 +
  565 + // Build the correct query to get all the information of the given table.
  566 + QString sQuery = m_qhTableInfo.value( m_dataBase.driver()->dbmsType() ).arg( _table );
  567 + if( !sQuery.isNull() && !sQuery.isEmpty() )
  568 + {
  569 + QSqlQuery oQuery( this->selectDatabase() );
  570 + if( oQuery.exec( sQuery ) )
  571 + {
  572 + while( oQuery.next() )
  573 + {
  574 + QString sFieldName = oQuery.value( 0 ).toString();
  575 + QVariant::Type vFieldType = ConversionUtils::stringToQvarType( oQuery.value( 1 ).toString() );
  576 +
  577 + qhResult.insert( sFieldName, vFieldType );
  578 + }
  579 + }
  580 + else
  581 + {
  582 + // The query failed. Lets figure out why..
  583 + LogDebug( "[DbConnector::getFieldNamesAndTypes]", QString( "The query reported an error : \n %1 for error : %2 \n" )
  584 + .arg( oQuery.lastError().text() )
  585 + .arg( sQuery ) );
  586 + }
  587 + }
  588 +
  589 + return qhResult;
  590 +}
  591 +
  592 +QSqlDatabase DbConnector::selectDatabase() const
  593 +{
  594 + QSqlDatabase dbResult;
  595 +
  596 + if ( !m_sDbIdentifier.isEmpty() )
  597 + {
  598 + dbResult = QSqlDatabase::database( m_sDbIdentifier );
  599 + }
  600 + else
  601 + {
  602 + dbResult = m_dataBase;
  603 + }
  604 +
  605 + return dbResult;
  606 +}
  607 +
  608 +bool DbConnector::associate( const QString& tableName, const QString& filterField, const QList<QVariant>& filterValueList, const QString& associateToField, const QVariant& associateToValue )
  609 +{
  610 + static const QString sQuery("UPDATE %1 SET %2 = %3 WHERE %4 IN ( %5 )");
  611 + if (filterValueList.isEmpty())
  612 + {
  613 + return true;
  614 + }
  615 +
  616 + QStringList valueList;
  617 + for (const auto& value : filterValueList)
  618 + {
  619 + valueList.append(this->toSqlValueString(value));
  620 + }
  621 + const auto q = this->executeQuery( sQuery
  622 + .arg(this->quoteTableName(tableName))
  623 + .arg(this->quoteFieldName(associateToField))
  624 + .arg(this->toSqlValueString(associateToValue))
  625 + .arg(this->quoteFieldName(filterField))
  626 + .arg(this->toSqlValueString(filterValueList)));
  627 + if (q && !q->lastError().isValid())
  628 + {
  629 + return true;
  630 + }
  631 + return false;
  632 +}
  633 +
  634 +bool DbConnector::disassociate( const QString& tableName, const QString& filterField, const QList<QVariant>& filterValueList, const QString& associateToField, const QVariant& associateToValue )
  635 +{
  636 + static const QString sDisassociateQuery("UPDATE %1 SET %2 = NULL WHERE %3 NOT IN ( %4 ) AND %2 = %5");
  637 + static const QString sDisassociateAllQuery("UPDATE %1 SET %2 = NULL WHERE %3 = %4");
  638 + QString sQuery;
  639 + if (filterValueList.isEmpty())
  640 + {
  641 + sQuery = sDisassociateAllQuery
  642 + .arg(this->quoteTableName(tableName))
  643 + .arg(this->quoteFieldName(associateToField))
  644 + .arg(this->quoteFieldName(associateToField))
  645 + .arg(this->toSqlValueString(associateToValue));
  646 + }
  647 + else
  648 + {
  649 + QStringList valueList;
  650 + sQuery = sDisassociateQuery
  651 + .arg(this->quoteTableName(tableName))
  652 + .arg(this->quoteFieldName(associateToField))
  653 + .arg(this->quoteFieldName(filterField))
  654 + .arg(this->toSqlValueString(filterValueList))
  655 + .arg(this->toSqlValueString(associateToValue));
  656 + }
  657 +
  658 + const auto q = this->executeQuery( sQuery );
  659 + if (q && !q->lastError().isValid())
  660 + {
  661 + return true;
  662 + }
  663 + return false;
  664 +}
  665 +
  666 +std::unique_ptr<QSqlQuery> DbConnector::executeQuery( const QString& sQuery ) const
  667 +{
  668 + std::unique_ptr<QSqlQuery> pQuery;
  669 +
  670 + QSqlDatabase mDb = this->selectDatabase();
  671 + if( mDb.isValid() && mDb.isOpen() )
  672 + {
  673 + pQuery.reset(new QSqlQuery( sQuery, mDb ));
  674 + const auto err = pQuery->lastError();
  675 + if(err.isValid())
  676 + {
  677 + QString queryString = pQuery->executedQuery();
  678 + if (queryString.isEmpty())
  679 + {
  680 + queryString = pQuery->lastQuery();
  681 + }
  682 + LogError("dbConnector::executeQuery",
  683 + QString( "Query '%1' gave error : '%2'" )
  684 + .arg(queryString)
  685 + .arg(err.text()));
  686 + }
  687 + }
  688 + else
  689 + {
  690 + LogError("dbConnector::executeQuery",
  691 + "Database is closed. Please connect first");
  692 + }
  693 +
  694 + return pQuery;
  695 +}
  696 +
  697 +QString DbConnector::toSqlValueString(const QVariant& value) const
  698 +{
  699 + LogDebug("DbConnector::toSqlValueString", QString("Value is %1 (type is %2)").arg(value.toString()).arg(value.type()));
  700 + switch(value.type())
  701 + {
  702 + case QVariant::Int:
  703 + return value.toString();
  704 + case QVariant::Uuid:
  705 + return "'" + value.toString().replace( "{", "" ).replace( "}", "" ) + "'";
  706 + case QVariant::List:
  707 + {
  708 + QStringList valueList;
  709 + for ( const auto& v : value.toList() )
  710 + {
  711 + valueList.append( this->toSqlValueString( v ) );
  712 + }
  713 + return valueList.join( "," );
  714 + }
  715 + default:
  716 + return "'" + value.toString() + "'";
  717 + }
  718 +}
  719 +
  720 +QHash<QString, QList<DbRelation*> > DbConnector::getRelations( const QStringList& tables )
  721 +{
  722 + QHash<QString, QList<DbRelation*> > qhResult;
  723 + /* First we get the type of database we're using. For each database type
  724 + * ( MySQL, PostGresQL or ORACLE ) the way of retrieving is different.
  725 + * We make sure the returned fields are always the same..
  726 + */
  727 +
  728 + // Check if the query database was build or not..
  729 + if( 0 == m_qhRelations.count() )
  730 + {
  731 + constructQueryHash();
  732 + }
  733 +
  734 + // Return the query of our database...
  735 + QString relQuery = m_qhRelations.value( m_dataBase.driver()->dbmsType() );
  736 + if( relQuery.isEmpty() ) /// Probably not a match or not implemented..
  737 + {
  738 + return QHash<QString, QList<DbRelation*>>();
  739 + }
  740 + else
  741 + {
  742 + // Retrieve the list of tables from the configuration :
  743 + QStringList tableList = tables;
  744 + if( tableList.isEmpty() && 0 == tableList.count() )
  745 + {
  746 + tableList = this->getTableNames();
  747 + }
  748 +
  749 + for( auto& table : tableList )
  750 + {
  751 + qhResult.insert( table, getRelationByTableName( table ) );
  752 + }
  753 + }
  754 +
  755 + return qhResult;
  756 +}
  757 +
  758 +void DbConnector::constructQueryHash()
  759 +{
  760 + m_qhRelations.insert(QSqlDriver::UnknownDbms, "");
  761 + m_qhRelations.insert(QSqlDriver::MSSqlServer, "");
  762 +
  763 + m_qhRelations.insert(QSqlDriver::MySqlServer, "SELECT \
  764 + TABLE_SCHEMA AS schema_name, \
  765 + TABLE_NAME AS table_name, \
  766 + CONSTRAINT_NAME AS constraint_name, \
  767 + COLUMN_NAME AS index_column, \
  768 + REFERENCED_TABLE_NAME AS foreign_table, \
  769 + REFERENCED_COLUMN_NAME AS foreign_column \
  770 + FROM \
  771 + INFORMATION_SCHEMA.KEY_COLUMN_USAGE \
  772 + WHERE \
  773 + TABLE_SCHEMA = SCHEMA() \
  774 + AND \
  775 + TABLE_NAME = '%1' \
  776 + AND \
  777 + REFERENCED_TABLE_NAME IS NOT NULL" );
  778 +
  779 + // @todo : Different schemas in the same database, with the same constraints will screw this up...
  780 + m_qhRelations.insert(QSqlDriver::PostgreSQL, "SELECT DISTINCT \
  781 + n.nspname AS schema_name, \
  782 + pc.relname AS table_name, \
  783 + r.conname AS constraint_name, \
  784 + pc1.relname AS foreign_table, \
  785 + r.contype AS constraint_type, \
  786 + pg_catalog.pg_get_constraintdef(r.oid) as definition \
  787 + FROM \
  788 + pg_constraint AS r \
  789 + JOIN pg_catalog.pg_namespace n on n.oid = r.connamespace \
  790 + JOIN pg_class AS pc ON pc.oid = r.conrelid \
  791 + LEFT JOIN pg_class AS pc1 ON pc1.oid = r.confrelid \
  792 + WHERE \
  793 + r.contype = 'f' \
  794 + AND \
  795 + pc.relname = '%1' \
  796 + ORDER BY \
  797 + schema_name, \
  798 + table_name, \
  799 + foreign_table" );
  800 +
  801 + m_qhRelations.insert(QSqlDriver::Oracle, "");
  802 + m_qhRelations.insert(QSqlDriver::Sybase, "");
  803 + m_qhRelations.insert(QSqlDriver::SQLite, "");
  804 + m_qhRelations.insert(QSqlDriver::Interbase, "");
  805 + m_qhRelations.insert(QSqlDriver::DB2, "" );
  806 +
  807 + // ============================================================================================================
  808 + // Build the table Information Query Hash
  809 + // ============================================================================================================
  810 +
  811 + m_qhTableInfo.insert(QSqlDriver::UnknownDbms, "");
  812 + m_qhTableInfo.insert(QSqlDriver::MSSqlServer, "");
  813 + m_qhTableInfo.insert(QSqlDriver::MySqlServer, "SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_NAME = '%1'" );
  814 + m_qhTableInfo.insert(QSqlDriver::PostgreSQL, "SELECT COLUMN_NAME, DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_NAME = '%1'" );
  815 + m_qhTableInfo.insert(QSqlDriver::Oracle, "");
  816 + m_qhTableInfo.insert(QSqlDriver::Sybase, "");
  817 + m_qhTableInfo.insert(QSqlDriver::SQLite, "");
  818 + m_qhTableInfo.insert(QSqlDriver::Interbase, "");
  819 + m_qhTableInfo.insert(QSqlDriver::DB2, "" );
  820 +}
  821 +
  822 +QList<DbRelation*> DbConnector::getRelationByTableName( const QString& tableName )
  823 +{
  824 + QList<DbRelation*> qlResult;
  825 +
  826 + if ( 0 == m_qhRelations.count() || 0 == m_qhTableInfo.count() )
  827 + {
  828 + this->constructQueryHash();
  829 + }
  830 +
  831 + // Return the query of our database...
  832 + QString relQuery = m_qhRelations.value( m_dataBase.driver()->dbmsType() );
  833 + if( !relQuery.isEmpty() )
  834 + {
  835 + QSqlQuery oQuery( this->selectDatabase() );
  836 + if( oQuery.exec( relQuery.arg( stripSchemaFromTable( tableName ) ) ) )
  837 + {
  838 + while( oQuery.next() )
  839 + {
  840 + DbRelation *pRel = new DbRelation();
  841 + pRel->setSchemaName( oQuery.record().value( "schema_name" ).toString() );
  842 + pRel->setTableName( oQuery.record().value( "table_name" ).toString() );
  843 + pRel->setConstraintName( oQuery.record().value( "constraint_name" ).toString() );
  844 +
  845 + switch ( m_dataBase.driver()->dbmsType() )
  846 + {
  847 + case QSqlDriver::UnknownDbms:
  848 + case QSqlDriver::MSSqlServer:
  849 + case QSqlDriver::Oracle:
  850 + case QSqlDriver::Sybase:
  851 + case QSqlDriver::SQLite:
  852 + case QSqlDriver::Interbase:
  853 + case QSqlDriver::DB2:
  854 + pRel->setIndexColumn( "" );
  855 + break;
  856 + case QSqlDriver::PostgreSQL:
  857 + pRel->setIndexColumn(
  858 + parseDefinition(
  859 + oQuery.record().value( "definition" ).toString() ) );
  860 +
  861 + // Get schema name associated with the foreign table, instead of the target-table.
  862 + pRel->setForeignTable( getSchemaByTable( oQuery.record().value( "foreign_table" ).toString() ) );
  863 +
  864 + break;
  865 + case QSqlDriver::MySqlServer:
  866 + pRel->setIndexColumn( oQuery.record().value("index_column").toString() );
  867 + pRel->setForeignTable( oQuery.record().value( "foreign_table" ).toString() );
  868 + break;
  869 +
  870 + }
  871 + pRel->setForeignPrimKey( this->getPrimaryKey( oQuery.record().value( "foreign_table" ).toString() ) );
  872 +
  873 + qlResult.append( pRel );
  874 + LogDebug( "[DbConnector::getRelationByTableName]", QString( "Relation->schemaName : %1" ).arg( pRel->schemaName() ) );
  875 + LogDebug( "[DbConnector::getRelationByTableName]", QString( "Relation->tableName : %1" ).arg( pRel->tableName() ) );
  876 + LogDebug( "[DbConnector::getRelationByTableName]", QString( "Relation->constraintName : %1" ).arg( pRel->constraintName() ) );
  877 + LogDebug( "[DbConnector::getRelationByTableName]", QString( "Relation->foreignTable : %1" ).arg( pRel->foreignTable() ) );
  878 + LogDebug( "[DbConnector::getRelationByTableName]", QString( "Relation->indexColumn : %1" ).arg( pRel->indexColumn() ) );
  879 + }
  880 + }
  881 + else
  882 + {
  883 + LogInfo( "[DbConnector::getRelationByTableName]", QString( "There were no relations for table : %1" ).arg( tableName ) );
  884 + LogInfo( "[DbConnector::getRelationByTableName]", QString( "Query executed : %1" ).arg( oQuery.lastQuery() ) );
  885 + LogInfo( "[DbConnector::getRelationByTableName]", QString( "Query reported the following error : %1" ).arg( oQuery.lastError().text() ) );
  886 + }
  887 + }
  888 + else
  889 + {
  890 + LogInfo( "[DbConnector::getRelationByTableName]", QString( "No query was found for database type : %1" ).arg( m_dataBase.driverName() ) );
  891 + }
  892 +
  893 + return qlResult;
  894 +}
  895 +
  896 +QString DbConnector::parseDefinition( const QString& definition ) const
  897 +{
  898 + QString _definition = definition;
  899 + QString leftPattern = "FOREIGN KEY (";
  900 + QString rightPattern = ") REFERENCES";
  901 +
  902 + QString noLeft = _definition.replace( leftPattern, "" );
  903 +
  904 + return noLeft.left( noLeft.indexOf( rightPattern ) ).replace( "\"", "" );
  905 +}
  906 +
  907 +QString DbConnector::stripSchemaFromTable( const QString& tableName ) const
  908 +{
  909 + QString sResult = tableName;
  910 +
  911 + if( tableName.contains(".") )
  912 + {
  913 + sResult = tableName.right( tableName.size() - tableName.indexOf( "." ) - 1 ).replace("\"", "");
  914 + }
  915 +
  916 + return sResult;
  917 +}
  918 +
  919 +QVariant DbConnector::getRelationKey( const QString& tableName,
  920 + const QString& filterField,
  921 + const QVariant& filterValue,
  922 + const QString& foreignPrimKey ) const
  923 +{
  924 + QString l_primKey;
  925 + if( foreignPrimKey.isNull() || foreignPrimKey.isEmpty() )
  926 + {
  927 + // Retrieve the primary key of the given table.
  928 + l_primKey = this->getPrimaryKey( tableName );
  929 + }
  930 + else
  931 + {
  932 + l_primKey = foreignPrimKey;
  933 + }
  934 +
  935 + // If none was found, return an empty QVariant
  936 + if ( l_primKey.isNull() )
  937 + {
  938 + LogInfo( "[DbConnector::getRelationKey]",
  939 + QString( "There was no primary keyfield found for : %1" )
  940 + .arg( tableName ) );
  941 + return QVariant();
  942 + }
  943 +
  944 + // Build the QueryString with the parameters given :
  945 + // %1 - Primary Key ( l_primKey )
  946 + // %2 - tableName (Quoted if needed)
  947 + // %3 - filterField
  948 + // %4 - filterValue
  949 + QString sQuery = QString( "SELECT \"%1\" FROM %2 WHERE \"%3\" = '%4'" )
  950 + .arg( l_primKey, this->quoteTableName(tableName), filterField, filterValue.toString().replace("{", "").replace( "}", "" ) );
  951 +
  952 + QVariant l_resPK;
  953 + QSqlQuery oQuery( m_dataBase );
  954 + if( oQuery.exec( sQuery ) && oQuery.first() && oQuery.size() == 1 )
  955 + {
  956 + l_resPK = oQuery.value( l_primKey );
  957 + }
  958 + else if( oQuery.size() > 1 )
  959 + {
  960 + LogInfo( "[DbConnector::getRelationKey]",
  961 + QString( "Multiple records found for value : %1" )
  962 + .arg( filterValue.toString() ) );
  963 + }
  964 + else
  965 + {
  966 + LogInfo( "[DbConnector::getRelationKey]",
  967 + QString( "There was no primary key retrieved for : %1" )
  968 + .arg( sQuery ) );
  969 + }
  970 + return l_resPK;
  971 +}
  972 +
  973 +QString DbConnector::getPrimaryKey( const QString& tableName ) const
  974 +{
  975 + switch( m_dataBase.driver()->dbmsType() )
  976 + {
  977 + case QSqlDriver::MySqlServer:
  978 + return this->getPrimaryKeyMySQL( tableName );
  979 + case QSqlDriver::PostgreSQL:
  980 + return this->getPrimaryKeyPostGreSQL( tableName );
  981 + case QSqlDriver::UnknownDbms:
  982 + case QSqlDriver::MSSqlServer:
  983 + case QSqlDriver::Oracle:
  984 + case QSqlDriver::Sybase:
  985 + case QSqlDriver::SQLite:
  986 + case QSqlDriver::Interbase:
  987 + case QSqlDriver::DB2:
  988 + break;
  989 + }
  990 + return QString();
  991 +}
  992 +
  993 +QString DbConnector::getPrimaryKeyMySQL( const QString& tableName ) const
  994 +{
  995 + QString sQuery = QString( "SHOW INDEX FROM %1 WHERE Key_name = '%2'" )
  996 + .arg( tableName )
  997 + .arg( m_dataBase.primaryIndex( this->quoteTableName( tableName ) ).name() );
  998 +
  999 + QSqlQuery oQuery( m_dataBase );
  1000 + if( oQuery.exec( sQuery ) && oQuery.first() && oQuery.size() == 1 )
  1001 + {
  1002 + return oQuery.value( "Column_name" ).toString();
  1003 + }
  1004 +
  1005 + return QString();
  1006 +}
  1007 +
  1008 +QString DbConnector::getPrimaryKeyPostGreSQL( const QString& tableName ) const
  1009 +{
  1010 + QString sResult;
  1011 + QString _table;
  1012 + if( tableName.contains( "." ) )
  1013 + {
  1014 + _table = this->stripSchemaFromTable( tableName );
  1015 + }
  1016 + else
  1017 + {
  1018 + _table = tableName;
  1019 + }
  1020 +
  1021 + QString sQuery = QString("SELECT \
  1022 + c.column_name AS key_field, \
  1023 + c.data_type AS key_type, \
  1024 + tc.table_name AS table_name \
  1025 + FROM \
  1026 + information_schema.table_constraints tc \
  1027 + JOIN \
  1028 + information_schema.constraint_column_usage AS ccu \
  1029 + USING (constraint_schema, constraint_name) \
  1030 + JOIN \
  1031 + information_schema.columns AS c ON c.table_schema = tc.constraint_schema \
  1032 + AND \
  1033 + tc.table_name = c.table_name AND ccu.column_name = c.column_name \
  1034 + WHERE \
  1035 + constraint_type = 'PRIMARY KEY' \
  1036 + AND \
  1037 + tc.table_name = '%1'").arg( _table );
  1038 +
  1039 + QSqlQuery oQuery( m_dataBase );
  1040 + if( oQuery.exec( sQuery ) )
  1041 + {
  1042 + // Select the first record.
  1043 + oQuery.first();
  1044 + LogDebug( "[DbConnector::getPrimaryKeyPostGreSQL]",
  1045 + QString( "KeyField found.. : %1" ).arg( oQuery.value( "key_field" ).toString() ) );
  1046 + sResult = oQuery.value( "key_field" ).toString();
  1047 + }
  1048 + else
  1049 + {
  1050 + LogInfo( "[DbConnector::getPrimaryKeyPostGreSQL]",
  1051 + QString( "There was an error running the query : %1" ).arg( sQuery ) );
  1052 + LogInfo( "[DbConnector::getPrimaryKeyPostGreSQL]",
  1053 + QString( "Dblayer gave error : %1" ).arg( oQuery.lastError().text() ) );
  1054 + }
  1055 +
  1056 + return sResult;
  1057 +}
  1058 +
  1059 +QString DbConnector::quoteTableName( const QString& tableName ) const
  1060 +{
  1061 + QString sResult( tableName );
  1062 + switch( m_dataBase.driver()->dbmsType() )
  1063 + {
  1064 + case QSqlDriver::MySqlServer:
  1065 + return sResult;
  1066 +
  1067 + case QSqlDriver::PostgreSQL:
  1068 + if( -1 == sResult.indexOf( "\"" ) )
  1069 + {
  1070 + // Ok. Seems like it is not quoted.
  1071 + sResult = QString( "\"" + QString( sResult.replace('.', "\".\"" ) + "\"") );
  1072 + return sResult;
  1073 + }
  1074 + else
  1075 + {
  1076 + return sResult;
  1077 + }
  1078 +
  1079 + case QSqlDriver::UnknownDbms:
  1080 + case QSqlDriver::MSSqlServer:
  1081 + case QSqlDriver::Oracle:
  1082 + case QSqlDriver::Sybase:
  1083 + case QSqlDriver::SQLite:
  1084 + case QSqlDriver::Interbase:
  1085 + case QSqlDriver::DB2:
  1086 + LogInfo( "[OrmHandler::quoteTableName]",
  1087 + QString( "Unknown database requested : %1" )
  1088 + .arg( m_dataBase.driverName() ) );
  1089 + break;
  1090 + }
  1091 + return sResult;
  1092 +}
  1093 +
  1094 +QString DbConnector::quoteFieldName( const QString& fieldName ) const
  1095 +{
  1096 + QString sResult( fieldName );
  1097 + switch( m_dataBase.driver()->dbmsType() )
  1098 + {
  1099 + case QSqlDriver::MySqlServer:
  1100 + return sResult;
  1101 +
  1102 + case QSqlDriver::PostgreSQL:
  1103 + if( -1 == sResult.indexOf( "\"" ) )
  1104 + {
  1105 + // Ok. Seems like it is not quoted.
  1106 + sResult = QString( "\"%1\"" ).arg(fieldName);
  1107 + return sResult;
  1108 + }
  1109 + else
  1110 + {
  1111 + return sResult;
  1112 + }
  1113 +
  1114 + case QSqlDriver::UnknownDbms:
  1115 + case QSqlDriver::MSSqlServer:
  1116 + case QSqlDriver::Oracle:
  1117 + case QSqlDriver::Sybase:
  1118 + case QSqlDriver::SQLite:
  1119 + case QSqlDriver::Interbase:
  1120 + case QSqlDriver::DB2:
  1121 + LogInfo( "[OrmHandler::quoteFieldName]",
  1122 + QString( "Unknown database requested : %1" )
  1123 + .arg( m_dataBase.driverName() ) );
  1124 + break;
  1125 + }
  1126 + return sResult;
  1127 +}
  1128 +
  1129 +QString DbConnector::quoteFieldValue( const QVariant& value ) const
  1130 +{
  1131 + QString sResult( value.toString() );
  1132 + switch( value.type() )
  1133 + {
  1134 + case QVariant::Uuid:
  1135 + case QVariant::Date:
  1136 + case QVariant::DateTime:
  1137 + case QVariant::String:
  1138 + sResult = QString( "%1%2%1" ).arg( quoteChar() ).arg( sResult );
  1139 + break;
  1140 + default:
  1141 + break;
  1142 + }
  1143 +
  1144 + return sResult;
  1145 +}
  1146 +
  1147 +QString DbConnector::quoteChar() const
  1148 +{
  1149 + QString sResult = "";
  1150 + switch( m_dataBase.driver()->dbmsType() )
  1151 + {
  1152 + case QSqlDriver::MySqlServer:
  1153 + return sResult;
  1154 +
  1155 + case QSqlDriver::PostgreSQL:
  1156 + sResult = QString( "'" );
  1157 + return sResult;
  1158 +
  1159 + case QSqlDriver::UnknownDbms:
  1160 + case QSqlDriver::MSSqlServer:
  1161 + case QSqlDriver::Oracle:
  1162 + case QSqlDriver::Sybase:
  1163 + case QSqlDriver::SQLite:
  1164 + case QSqlDriver::Interbase:
  1165 + case QSqlDriver::DB2:
  1166 + LogInfo( "[OrmHandler::quoteFieldName]",
  1167 + QString( "Unsupported database requested : %1" )
  1168 + .arg( m_dataBase.driverName() ) );
  1169 + break;
  1170 + }
  1171 + return sResult;
  1172 +}
  1173 +
  1174 +QString DbConnector::getSchemaByTable( const QString& tableName ) const
  1175 +{
  1176 + QString sResult;
  1177 + QString sQuery;
  1178 +
  1179 + switch( m_dataBase.driver()->dbmsType() )
  1180 + {
  1181 + case QSqlDriver::PostgreSQL:
  1182 + sQuery = QString( "SELECT \
  1183 + table_schema, \
  1184 + table_name \
  1185 + FROM \
  1186 + information_schema.tables \
  1187 + WHERE \
  1188 + table_name = '%1'" )
  1189 + .arg( tableName );
  1190 + break;
  1191 + case QSqlDriver::MySqlServer:
  1192 + case QSqlDriver::UnknownDbms:
  1193 + case QSqlDriver::MSSqlServer:
  1194 + case QSqlDriver::Oracle:
  1195 + case QSqlDriver::Sybase:
  1196 + case QSqlDriver::SQLite:
  1197 + case QSqlDriver::Interbase:
  1198 + case QSqlDriver::DB2:
  1199 + LogInfo( "[OrmHandler::quoteTableName]",
  1200 + QString( "Unknown database requested : %1" )
  1201 + .arg( m_dataBase.driverName() ) );
  1202 + break;
  1203 + }
  1204 +
  1205 + // If no joy, return an empty string.
  1206 + if( sQuery.isEmpty() )
  1207 + {
  1208 + return QString();
  1209 + }
  1210 +
  1211 + // Geronimo!
  1212 + QSqlQuery oQuery( m_dataBase );
  1213 + if( oQuery.exec( sQuery ) )
  1214 + {
  1215 + if( oQuery.first() )
  1216 + {
  1217 + sResult = oQuery.value( "table_schema" ).toString() + QString( "." ) + tableName;
  1218 + }
  1219 +
  1220 + }
  1221 + return sResult;
  1222 +}
  1223 +
  1224 +void DbConnector::slotDbConnected( const QString& db_name, bool is_open )
  1225 +{
  1226 + // We're not using the name (yet) already there for future implementations.
  1227 + Q_UNUSED( db_name );
  1228 + m_bDbOpen = is_open;
  1229 +}
  1230 +
  1231 +void DbConnector::startDbWatchDog( const int check_interval )
  1232 +{
  1233 + if( m_pWatchDog.isNull() )
  1234 + {
  1235 + m_pWatchDog = new DbConnectionWatchDog( check_interval, this );
  1236 + #if( QT_VERSION >= QT_VERSION_CHECK( 5, 2, 0) )
  1237 + connect( m_pWatchDog.data(), &DbConnectionWatchDog::signalDbConnected, this, &DbConnector::slotDbConnected );
  1238 + #else
  1239 + connect( m_pWatchDog.data(), SIGNAL( signalDbConnected( const QString&, bool ) ), this, SLOT( slotDbConnected( const QString& bool ) ) );
  1240 + #endif
  1241 + }
  1242 +
  1243 + if( m_pWatchDog )
  1244 + {
  1245 + m_pWatchDog->start();
  1246 + LogInfo( "[DbConnector::startDbWatchDog]", QString( "Database Watchdog started with interval : %1" ).arg( check_interval ) );
  1247 + }
  1248 +}
src/dbconnector.h 0 → 100644
  1 +++ a/src/dbconnector.h
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +#ifndef OSDEV_COMPONENTS_DBCONNECTOR_H
  24 +#define OSDEV_COMPONENTS_DBCONNECTOR_H
  25 +
  26 +#include "dcxmlconfig.h"
  27 +#include "dbconnectionwatchdog.h"
  28 +
  29 +#include <QObject>
  30 +#include <QSqlDatabase>
  31 +#include <QVariant>
  32 +#include <QList>
  33 +#include <QSqlDriver>
  34 +#include <QSqlRecord>
  35 +#include <QSharedPointer>
  36 +
  37 +#include <memory>
  38 +
  39 +class QSqlQuery;
  40 +class QString;
  41 +class QStringList;
  42 +template<class K, class V> class QHash;
  43 +class QSqlDatabase;
  44 +
  45 +namespace osdev {
  46 +namespace components {
  47 +
  48 +class DbRelation;
  49 +
  50 +/**
  51 + * @brief Database connector
  52 + */
  53 +class DbConnector : public QObject
  54 +{
  55 + Q_OBJECT
  56 +
  57 +public:
  58 +
  59 + /*! Default Ctor */
  60 + DbConnector();
  61 +
  62 + /**
  63 + * @brief Ctor with DatabaseIdentifier
  64 + * @param sDbIdentifier Database identifier
  65 + */
  66 + DbConnector(const QString& sDbIdentifier);
  67 +
  68 + /*!
  69 + * @brief Constructor with connection information
  70 + * @param sUserName Username
  71 + * @param sPassword Password
  72 + * @param sDatabaseName Name of the database
  73 + * @param sHostName Name of the host
  74 + * @param sDbType Type of database. Could be one of the string, supported by QSQLDriver.
  75 + * @param sDbIdentifier Used internally to distinguish between connections.
  76 + * If given and already existing, it will be overwritten,
  77 + * else a new connection will be created.
  78 + */
  79 + DbConnector(const QString& sUserName,
  80 + const QString& sPassword,
  81 + const QString& sDatabaseName,
  82 + const QString& sHostName,
  83 + const QString& sDbType,
  84 + const QString& sDbIdentifier );
  85 +
  86 + /// copy-constructor
  87 + DbConnector( const DbConnector& source );
  88 + /// assignment operator
  89 + DbConnector& operator=( const DbConnector& ) = delete;
  90 + /// move-constructor
  91 + DbConnector( DbConnector&& ) = delete;
  92 + /// move operator
  93 + DbConnector& operator=(DbConnector&&) = delete;
  94 +
  95 + /**
  96 + * @brief Ctor with connection Information packed in a nice QHash
  97 + * @param _qhCredentials Credentials
  98 + */
  99 + DbConnector( const QHash<QString, QString>& _qhCredentials );
  100 +
  101 + /*! Dtor */
  102 + virtual ~DbConnector();
  103 +
  104 + /**
  105 + * @brief Connect to the database
  106 + * @return True if connection is succesful, false otherwise
  107 + */
  108 + bool connectDatabase();
  109 +
  110 + /**
  111 + * @brief Set the username
  112 + * @param sUserName Username
  113 + */
  114 + void setUserName(const QString& sUserName );
  115 +
  116 + /// @return Current username
  117 + QString getUsername() const;
  118 +
  119 + /**
  120 + * @brief Set the password
  121 + * @param sPassword Password
  122 + */
  123 + void setPassword( const QString& sPassword );
  124 +
  125 + /// @return Current password
  126 + QString getPassWord() const;
  127 +
  128 + /**
  129 + * @brief Set the database name
  130 + * @param sDatabaseName Database name
  131 + */
  132 + void setDatabaseName(const QString& sDatabaseName );
  133 +
  134 + /// @return Current database name
  135 + QString getDatabaseName() const;
  136 +
  137 + /**
  138 + * @brief Set the hostname where the database resides
  139 + * @param sHostname Hostname
  140 + */
  141 + void setHostName( const QString& sHostname );
  142 +
  143 + /// @return Current hostname
  144 + QString getHostName() const;
  145 +
  146 + /**
  147 + * @brief Set the type of database
  148 + * @param sDbType Type of database
  149 + */
  150 + void setDbType(const QString& sDbType );
  151 +
  152 + /// @return Current type of database
  153 + QString getDbType() const;
  154 +
  155 + /**
  156 + * @brief Set the database identifier
  157 + * @param sDbIdentifier Database identifier
  158 + */
  159 + void setDbIdentifier( const QString& sDbIdentifier );
  160 +
  161 + /// @return Current database identifier
  162 + QString getDbIdentifier() const;
  163 +
  164 + /// @return Get the last error that occurred
  165 + QString getLastError() const;
  166 +
  167 + /**
  168 + * @brief Set credentials from a configuration
  169 + * @param _qhCredentials Hash containing the credentials
  170 + */
  171 + void setCredentials(const QHash<DCXmlConfig::eDbSettings, QString>& _qhCredentials );
  172 +
  173 + /// @return Current QSqlDatabase instance
  174 + QSqlDatabase getDatabase() const;
  175 +
  176 + /*!
  177 + * \brief Indicates wether the database supports transactions.
  178 + * \return True if so. False if not, or the database is closed (will check).
  179 + */
  180 + bool supportTransactions() const;
  181 +
  182 + /*!
  183 + * \brief Implemented for convenience. This will start a transaction.
  184 + * (See also supportTransactions() before calling this method );
  185 + * \return True if successful( i.e. If the driver supports it). False if it failed.
  186 + */
  187 + bool transactionBegin();
  188 +
  189 + /*!
  190 + * \brief Implemented for convenience. This will commit a transaction.
  191 + * (See also supportTransactions() before calling this method );
  192 + * \return True if successful( i.e. If the driver supports it). False if it failed.
  193 + */
  194 + bool transactionCommit();
  195 +
  196 + /*!
  197 + * \brief Implemented for convenience. This will rollback a transaction.
  198 + * (See also supportTransactions() before calling this method );
  199 + * \return True if successful( i.e. If the driver supports it). False if it failed.
  200 + */
  201 + bool transactionRollback();
  202 +
  203 + /**
  204 + * @brief Creates a new record.
  205 + * @param record to insert into sTable. All field info and values are housed
  206 + * inside the object......
  207 + * @return True on success. False on Failure.
  208 + */
  209 + bool createRecord( const QSqlRecord& record, const QString& sTable );
  210 +
  211 + /**
  212 + * @brief Create a new record
  213 + * @param sFields Comma-separated list of fields
  214 + * @param sValues Comma-seperated list of value
  215 + * @param sTable Database table to change
  216 + * @return True on success. False on failure.
  217 + */
  218 + bool createRecord( const QString& sFields,
  219 + const QString& sValues,
  220 + const QString& sTable );
  221 +
  222 + /**
  223 + * @brief Perform a read-query on the database.
  224 + * @param sQuery SQL query to perform
  225 + * @return QSqlQuery object pointer containing the result of the query.
  226 + * The caller is responsible for cleaning up the object after processing.
  227 + */
  228 + QSqlQuery* readRecord( const QString& sQuery );
  229 +
  230 + /**
  231 + * @brief Update a field in an existing record
  232 + * @param sIdNumber Value to match to locate the record
  233 + * @param sIdField Field that must contain sIdNumber
  234 + * @param sValue New value
  235 + * @param sFieldName Field name in which so store sValue
  236 + * @param sTable Table in which to search/replace
  237 + * @return True on success. False on failure, use getLastError() to retrieve
  238 + * the reason for failure
  239 + */
  240 + bool updateRecord( const QString& sIdNumber,
  241 + const QString& sIdField,
  242 + const QString& sValue,
  243 + const QString& sFieldName,
  244 + const QString& sTable );
  245 +
  246 + /**
  247 + * @brief Delete the selected record
  248 + * @param sIdNumber Value to match to locate the record
  249 + * @param sIdField Field that must contain sIdNumber
  250 + * @param sTable Table in which to delete
  251 + * @return True on success. False on failure, use getLastError() to retrieve
  252 + * the reason for failure
  253 + */
  254 + bool deleteRecord(const QString& sIdNumber,
  255 + const QString& sIdField,
  256 + const QString& sTable );
  257 +
  258 + // Helper methods
  259 + /**
  260 + * @brief Clear a whole table
  261 + * @param sTableName Name of the table
  262 + * @return True on success. False on failure, use getLastError() to retrieve
  263 + * the reason for failure
  264 + */
  265 + bool clearTable( const QString& sTableName );
  266 +
  267 + /**
  268 + * @brief Read all data for a specific table
  269 + * @param sTable Table to read
  270 + * @return QSqlQuery object pointer containing the result of the query.
  271 + * The caller is responsible for cleaning up the object after processing.
  272 + */
  273 + QSqlQuery* readTableData( const QString& sTable ) const;
  274 +
  275 + /**
  276 + * @brief Gets the number of records for the specified table
  277 + * @param sTable Table to read
  278 + * @return Number of records in that table
  279 + */
  280 + int getNumRecords( const QString& sTable ) const;
  281 +
  282 + /**
  283 + * @brief Check if the given list of values for a specific field in a specific table are found as records in the database.
  284 + * @param sTable Table to query.
  285 + * @param sField The field name for which the values are checked.
  286 + * @param slFieldValues The values that are searched for.
  287 + * @return true if all values are found or the slFieldValues list was empty, false otherwise.
  288 + */
  289 + bool recordsExist( const QString& sTable, const QString& sField, const QList<QVariant>& slFieldValues) const;
  290 +
  291 + // Specific Meta-Data functionality
  292 + /**
  293 + * @brief Retrieve all table names
  294 + * @return List of table names
  295 + */
  296 + QStringList getTableNames() const;
  297 +
  298 + /*!
  299 + * \brief Removes the Schema from the variable. It searches
  300 + * for a pattern like <Schema>.<Table> and chops off
  301 + * the part before (including the) point.
  302 + * \param tableName - The name of the table (including the schemaname).
  303 + * \return Table name. Check for .isEmpty() for success.
  304 + */
  305 + QString stripSchemaFromTable( const QString& tableName ) const;
  306 +
  307 + /**
  308 + * @brief Retrieve the field-names for a table
  309 + * @param tableName Name of the table
  310 + * @return List of fields
  311 + */
  312 + QStringList getFieldNames( const QString& tableName ) const;
  313 +
  314 + /**
  315 + * @brief Retrieve the field-names and types for a table
  316 + * @param tableName Name of the table
  317 + * @return Hash of field-name => type
  318 + */
  319 + QHash<QString, QVariant::Type> getFieldNamesAndTypes(
  320 + const QString& tableName ) const;
  321 +
  322 + /*!
  323 + * \brief Retrieves the relations as used between two tables.
  324 + * \return A Hash containing the relations by tablename.
  325 + */
  326 + QHash<QString, QList<DbRelation*> > getRelations( const QStringList& tables = QStringList() );
  327 +
  328 + /*!
  329 + * \brief Get the relations belonging to the given table name.
  330 + * \param tableName The table we like to get the constraints from.
  331 + * \return A List of all relation objects. Ownership of the pointers is
  332 + * transferred to the caller. The caller is responsible for cleaning up the objects.
  333 + */
  334 + QList<DbRelation*> getRelationByTableName( const QString& tableName );
  335 +
  336 + /*!
  337 + * \brief Retrieve a primaryKey, based on the filter settings
  338 + * \param tableName - The table we want the primary key from.
  339 + * \param filterField - The fieldname we like to filter on.
  340 + * \param filterValue - The value we like to filter on.
  341 + * \return The value of the primary key on success or an empty QVariant
  342 + */
  343 + QVariant getRelationKey(const QString& tableName, const QString& filterField, const QVariant& filterValue , const QString &foreignPrimKey = QString() ) const;
  344 +
  345 + /*!
  346 + * \brief Gets the primary key from the given table
  347 + * \param tableName - The table we would like the primary key from.
  348 + * \return The name of the key-field of the given table. It will return an empty string
  349 + * if the table doesn't have a primary key or the table doesn't exist.
  350 + */
  351 + QString getPrimaryKey( const QString& tableName ) const;
  352 +
  353 + /*!
  354 + * \brief Some databases are quite picky when it comes to tables and scheme's
  355 + * Based on the database we have connected, we quote the tablename
  356 + * to satisfy the db-driver.
  357 + * \param tableName - the tablename as given from the database layer.
  358 + * \return The quoted tablename.
  359 + */
  360 + QString quoteTableName( const QString& tableName ) const;
  361 +
  362 + /*!
  363 + * \brief Quote a fieldname if necessary.
  364 + * Based on the database we have connected, we quote the fieldname
  365 + * to satisfy the db-driver.
  366 + * \param fieldName - the fieldname to quote.
  367 + * \return The quoted fieldname.
  368 + */
  369 + QString quoteFieldName(const QString& fieldName ) const;
  370 +
  371 + /*!
  372 + * \brief Quote a fieldName if the type demands it.
  373 + * \param The value as QVariant. Based on its type it will receive quotes around it.
  374 + * \return The quote fieldValue
  375 + */
  376 + QString quoteFieldValue( const QVariant& value ) const;
  377 +
  378 + /*!
  379 + * \brief Each database has its own "quircks" when it comes to quotes.
  380 + * Based on the database type, the correct quote-character is returned
  381 + * \return Th quote-character based on the database type.
  382 + */
  383 + QString quoteChar() const;
  384 +
  385 + /*!
  386 + * \brief Some databases want to use the schema in front af the table name.
  387 + * \param tableName - The name of the table we want to retrieve the schema for.
  388 + * \return The Schema name as QString or an empty QString if unsuccessfull
  389 + */
  390 + QString getSchemaByTable( const QString& tableName ) const;
  391 +
  392 + /*!
  393 + * \brief Select the database on which to perform operations
  394 + * \return Selected database
  395 + */
  396 + QSqlDatabase selectDatabase() const;
  397 +
  398 + /*!
  399 + * \brief Associate a list of records to a specific foreign record.
  400 + * \param tableName The table to use.
  401 + * \param filterField The fieldname that is filtered on.
  402 + * \param filterValueList A comma separated list of filter values that determine the set of records the assocation is set to.
  403 + * \param associateToField The foreign key field.
  404 + * \param associateToValue The foreign record that the list of records is associated to.
  405 + * \return true if assocation is successful, false if not.
  406 + */
  407 + bool associate( const QString& tableName, const QString& filterField, const QList<QVariant>& filterValueList, const QString& associateToField, const QVariant& associateToValue );
  408 +
  409 + /*!
  410 + * \brief Disassociate records that do not belong to the given list but do have an assocation to the given foreign record.
  411 + * \param tableName The table to use.
  412 + * \param filterField The fieldname that is filtered on.
  413 + * \param filterValueList A comma separated list of filter values.
  414 + * The records that do have an assocation to the given foreign record and are not part of the list will be disassociated.
  415 + * \param associateToField The foreign key field.
  416 + * \param associateToValue The foreign record from which records that are not part of the given list are disassociated from.
  417 + * \return true if disassocation is successful, false if not.
  418 + */
  419 + bool disassociate( const QString& tableName, const QString& filterField, const QList<QVariant>& filterValueList, const QString& associateToField, const QVariant& associateToValue );
  420 +
  421 + /*!
  422 + * \brief Start a database connection watchdog. On certain times (5 secs default) all database connections are checked and reopened if necessary.
  423 + */
  424 + void startDbWatchDog( const int check_interval );
  425 +
  426 + /*!
  427 + * \brief Indicates the current database open or closed. It will be set by the watchdog during operation and the connection method during the initialization.
  428 + * \return True if database is open, False if not. If it is not supported by the database connection, we assume always True. The error from the database driver should be checked.
  429 + */
  430 + bool isOpen() const { return m_bDbOpen; }
  431 +
  432 +private:
  433 + /*!
  434 + * \brief Execute a query on a database.
  435 + * \param sQuery Query to execute.
  436 + * \return A pointer to the executed QSqlQuery.
  437 + * \retval nullptr Database is not connected to or is not valid.
  438 + * \note The caller is responsible for disposing of the returned QSqlQuery object before the database is destroyed!
  439 + */
  440 + std::unique_ptr<QSqlQuery> executeQuery( const QString& sQuery ) const;
  441 +
  442 + /*!
  443 + * \brief Convert a QVariant to an Sql value string.
  444 + * \return Sql representation of the value.
  445 + */
  446 + QString toSqlValueString(const QVariant& value) const;
  447 +
  448 + /*!
  449 + * \brief Retrieve the Primary key-field from the a specific table
  450 + * in a MySQL Database. Called by getPrimaryKey..
  451 + * \param tableName - The name of the table we're investigating.
  452 + * \return The primary key field name for the specified the table name.
  453 + */
  454 + QString getPrimaryKeyMySQL( const QString& tableName ) const;
  455 +
  456 + /*!
  457 + * \brief Retrieve the Primary key-field from the a specific table
  458 + * in a PostGreSQL Database. Called by getPrimaryKey..
  459 + * \param tableName - The name of the table we're investigating.
  460 + * \return The primary key field name for the specified the table name.
  461 + */
  462 + QString getPrimaryKeyPostGreSQL( const QString& tableName ) const;
  463 +
  464 + /**
  465 + * @brief Parses the specified definition.
  466 + * @param definition The definition to parse.
  467 + * @return the indexColumn as defined in the definition (Pleonasm someone?)
  468 + */
  469 + QString parseDefinition( const QString& definition ) const;
  470 +
  471 + /**
  472 + * @brief Build the hash table, containing the relation queries for each type of database.
  473 + */
  474 + void constructQueryHash();
  475 +
  476 + QPointer<DbConnectionWatchDog> m_pWatchDog;
  477 +
  478 + QSqlDatabase m_dataBase; ///< Used database when a DbIdentifier is specified
  479 +
  480 + QString m_sUserName; ///< Username
  481 + QString m_sPassword; ///< Password
  482 + QString m_sDatabaseName; ///< Name of the database
  483 + QString m_sHostName; ///< Host on which the database runs
  484 + QString m_sDbType; ///< Type of database
  485 + QString m_sDbIdentifier; ///< Identifier for the database
  486 + bool m_bDbOpen; ///< Identifying the database open or closed.
  487 +
  488 + QHash<QSqlDriver::DbmsType, QString> m_qhRelations; ///< Hash used to store the Relations queries by database type.
  489 + QHash<QSqlDriver::DbmsType, QString> m_qhTableInfo; ///< Hash used to store the TableMetaInfo queries by database type.
  490 +
  491 +private slots:
  492 + void slotDbConnected( const QString& db_name, bool is_open );
  493 +
  494 +};
  495 +
  496 +} // End namespace components
  497 +} // End namespace osdev
  498 +
  499 +#endif /* OSDEV_COMPONENTS_DBCONNECTOR_H */
src/dbrelation.cpp 0 → 100644
  1 +++ a/src/dbrelation.cpp
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +
  24 +#include "dbrelation.h"
  25 +
  26 +#include <QtDebug>
  27 +
  28 +using namespace osdev::components;
  29 +
  30 +DbRelation::DbRelation( QObject *_parent )
  31 + : QObject( _parent )
  32 + , m_schemaName( "" )
  33 + , m_tableName( "" )
  34 + , m_constraintName( "" )
  35 + , m_foreignTable( "" )
  36 + , m_indexColumn( "" )
  37 + , m_foreignPrimKey( "" )
  38 +{
  39 +}
src/dbrelation.h 0 → 100644
  1 +++ a/src/dbrelation.h
  1 +/* ****************************************************************************
  2 + * Copyright 2019 Open Systems Development BV *
  3 + * *
  4 + * Permission is hereby granted, free of charge, to any person obtaining a *
  5 + * copy of this software and associated documentation files (the "Software"), *
  6 + * to deal in the Software without restriction, including without limitation *
  7 + * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
  8 + * and/or sell copies of the Software, and to permit persons to whom the *
  9 + * Software is furnished to do so, subject to the following conditions: *
  10 + * *
  11 + * The above copyright notice and this permission notice shall be included in *
  12 + * all copies or substantial portions of the Software. *
  13 + * *
  14 + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
  15 + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
  16 + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
  17 + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
  18 + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
  19 + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
  20 + * DEALINGS IN THE SOFTWARE. *
  21 + * ***************************************************************************/
  22 +
  23 +#ifndef OSDEV_COMPONENTS_DBRELATION_H
  24 +#define OSDEV_COMPONENTS_DBRELATION_H
  25 +
  26 +#include <QObject>
  27 +#include <QString>
  28 +
  29 +namespace osdev {
  30 +namespace components {
  31 +
  32 +/*!
  33 + * \brief The DbRelation class is a container class holding the
  34 + * information regarding the relation between two tables.
  35 + */
  36 +
  37 +class DbRelation : public QObject
  38 +{
  39 + Q_OBJECT
  40 +
  41 +public:
  42 + DbRelation( QObject *parent = nullptr );
  43 +
  44 + /*!
  45 + * \brief Get the name of the database scheme
  46 + * \return The schemaname as QString. isEmpty() is true if none was set.
  47 + */
  48 + const QString& schemaName() const { return m_schemaName; }
  49 +
  50 + /*!
  51 + * \brief Set the name of the database scheme
  52 + * \param _schemaName - The name of the schema.
  53 + */
  54 + void setSchemaName( const QString &_schemaName ) { m_schemaName = _schemaName; }
  55 +
  56 + /*!
  57 + * \brief Get the name of the table this relation describes.
  58 + * \return The name of the table this relation describes.
  59 + */
  60 + const QString& tableName() const { return m_tableName; }
  61 +
  62 + /*!
  63 + * \brief Set the name of the table this relation describes.
  64 + * \param _tableName - The name of the table this relation describes.
  65 + */
  66 + void setTableName( const QString &_tableName ) { m_tableName = _tableName; }
  67 +
  68 + /*!
  69 + * \brief Get the name of the constraint this relation describes.
  70 + * \return the name of the constraint this relation describes.
  71 + */
  72 + const QString& constraintName() const { return m_constraintName; }
  73 +
  74 + /*!
  75 + * \brief Set the name of the constraint this relation describes.
  76 + * \param _constraintName - The name of the constraint this relation describes.
  77 + */
  78 + void setConstraintName( const QString &_constraintName ) { m_constraintName = _constraintName; }
  79 +
  80 + /*!
  81 + * \brief Get the name of the related table this relation describes.
  82 + * \return the name of the related table this relation describes.
  83 + */
  84 + const QString& foreignTable() const { return m_foreignTable; }
  85 +
  86 + /*!
  87 + * \brief Set the name of the related table this relation describes.
  88 + * \param _foreignTable - the name of the related table this relation describes.
  89 + */
  90 + void setForeignTable( const QString &_foreignTable ) { m_foreignTable = _foreignTable; }
  91 +
  92 + /*!
  93 + * \brief The name of the field on the maintable this relation describes.
  94 + * \return The name of the field on the maintable this relation describes.
  95 + */
  96 + const QString& indexColumn() const { return m_indexColumn; }
  97 +
  98 + /*!
  99 + * \brief Set the name of the field on the maintable this relation describes.
  100 + * \param _indexColumn - The name of the field on the maintable this relation describes.
  101 + */
  102 + void setIndexColumn( const QString &_indexColumn ) { m_indexColumn = _indexColumn; }
  103 +
  104 + /*!
  105 + * \brief Gets the name of the primary key field of the related table.
  106 + * \return The name of the primary key field of the related table.
  107 + */
  108 + const QString& foreignPrimKey() const { return m_foreignPrimKey; }
  109 +
  110 + /*!
  111 + * \brief Sets the name of the primary key field of the related table.
  112 + * \param _foreignPrimKey - The name of the primary key field of the related table.
  113 + */
  114 + void setForeignPrimKey( const QString &_foreignPrimKey ) { m_foreignPrimKey = _foreignPrimKey; }
  115 +
  116 + /*!
  117 + * \brief Return the relation structure as a QString for Debugging purposes
  118 + * \return The datastructure in QString representation.
  119 + */
  120 + QString asString() const { return QString( "SchemaName : %1 \nTableName : %2 \n ConstraintName : %3 \n ForeignTable : %4 \n IndexColumn : %5 \n Foreign Primary Key : %6")
  121 + .arg( m_schemaName, m_tableName, m_constraintName, m_foreignTable, m_indexColumn, m_foreignPrimKey ); }
  122 +
  123 +private:
  124 + QString m_schemaName; ///< The name of the database scheme
  125 + QString m_tableName; ///< The name of the target-table
  126 + QString m_constraintName; ///< The name of the constraint as known to the database
  127 + QString m_foreignTable; ///< The name of the table the target has a relation with
  128 + QString m_indexColumn; ///< The name of the column of the target table that holds the relation.
  129 + QString m_foreignPrimKey; ///< The name of the primary key-field of the related table.
  130 +};
  131 +
  132 +} /* End namespace components */
  133 +} /* End namespace osdev */
  134 +
  135 +#endif /* OSDEV_COMPONENTS_DBRELATION_H */
tests/CMakeLists.txt 0 → 100644
  1 +++ a/tests/CMakeLists.txt
  1 +cmake_minimum_required(VERSION 3.0)
  2 +LIST(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../cmake)
  3 +
  4 +include(projectheader)
  5 +project_header(test_logutils)
  6 +
  7 +include_directories( SYSTEM
  8 + ${CMAKE_CURRENT_SOURCE_DIR}/../../src
  9 +)
  10 +
  11 +include(compiler)
  12 +set(SRC_LIST
  13 +)
  14 +
  15 +# add_executable( ${PROJECT_NAME}
  16 +# ${SRC_LIST}
  17 +# )
  18 +
  19 +# target_link_libraries(
  20 +# ${PROJECT_NAME}
  21 +# )
  22 +
  23 +# set_target_properties( ${PROJECT_NAME} PROPERTIES
  24 +# RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin
  25 +# LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib
  26 +# ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/archive
  27 +# )
  28 +
  29 +# include(installation)
  30 +# install_application()