/* *openPilot Log - A FOSS Pilot Logbook Application *Copyright (C) 2020 Felix Turowsky * *This program is free software: you can redistribute it and/or modify *it under the terms of the GNU General Public License as published by *the Free Software Foundation, either version 3 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 General Public License for more details. * *You should have received a copy of the GNU General Public License *along with this program. If not, see . */ #include "db.h" #include "dbinfo.h" // Debug Makro #define DEB(expr) \ qDebug() << "db ::" << __func__ << "\t" << expr db::db() { } db::db(sql::tableName tn, int row_ID) { switch (tn) { case sql::flights: table = "flights"; break; case sql::pilots: table = "pilots"; break; case sql::aircraft: table = "aircraft"; break; case sql::tails: table = "tails"; break; case sql::airports: table = "airports"; break; } row_id = row_ID; QString statement = "SELECT COUNT(*) FROM " + table + " WHERE _rowid_="+QString::number(row_id); QSqlQuery q(statement); q.exec(); q.next(); int rows = q.value(0).toInt(); if(rows==0){ DEB("No entry found for row id: " << row_ID ); }else{ DEB("Retreiving data for row id: " << row_id); isValid = retreiveData(); } } db::db(sql::tableName tn, QMap newData) { switch (tn) { case sql::flights: table = "flights"; break; case sql::pilots: table = "pilots"; break; case sql::aircraft: table = "aircraft"; break; case sql::tails: table = "tails"; break; case sql::airports: table = "airports"; break; } //Do some checks auto in = dbInfo(); auto columns = in.format.value(table); QMap::iterator i; for (i = newData.begin(); i != newData.end(); ++i){ if(!columns.contains(i.key())){ DEB(newData); DEB(i.key()<< i.value() << "Not in column list for " << table <<". Removing."); newData.remove(i.key()); } } data = newData; } void db::setData(const QMap &value) { data = value; } /*! * \brief db::connect connects to the database via the default connection. * Can then be accessed globally with QSqlDatabase::database("qt_sql_default_connection") */ void db::connect() { const QString driver("QSQLITE"); if(QSqlDatabase::isDriverAvailable(driver)){ QDir directory("data"); QString databaseLocation = directory.filePath("logbook.db"); QSqlDatabase db = QSqlDatabase::addDatabase(driver); db.setDatabaseName(databaseLocation); if(!db.open()){ DEB("DatabaseConnect - ERROR: " << db.lastError().text()); }else{ DEB("Database connection established."); } }else{ DEB("DatabaseConnect - ERROR: no driver " << driver << " available"); } } /*! * \brief db::retreiveData retreives data from the database. * \return */ bool db::retreiveData() { const auto info = dbInfo(); QString statement = "SELECT * FROM " + table + " WHERE _rowid_="+QString::number(row_id); DEB("Executing SQL..."); DEB(statement); QSqlQuery q(statement); q.exec(); q.next(); for(int i=0; i < info.format.value(table).length(); i++){ data.insert(info.format.value(table)[i],q.value(i).toString()); } QString error = q.lastError().text(); if(error.length() > 2){ DEB("Error: " << q.lastError().text()); return false; }else{return true;} } /*! * \brief db::exists checks if a certain value exists in the database with a sqlite WHERE statement * \param table - Name of the table * \param column - Name of the column * \param value - The value to be checked * \return */ bool db::exists(QString column, QString table, QString checkColumn, QString value, sql::matchType match) { bool output = false; QString statement = "SELECT " + column + " FROM " + table + " WHERE " + checkColumn; switch (match) { case sql::exactMatch: statement += " = '" + value + QLatin1Char('\''); break; case sql::partialMatch: value.append(QLatin1Char('%')); value.prepend(QLatin1Char('%')); statement.append(" LIKE '" + value + QLatin1Char('\'')); break; } DEB(statement); QSqlQuery q(statement); q.exec(); if(!q.first()){ DEB("No result found. Check Query and Error."); DEB("Error: " << q.lastError().text()); }else{ DEB("Success. Found a result."); output = true; if(q.next()){ DEB("More than one result in Database for your query"); } } // Debug: q.first(); q.previous(); while(q.next()){ DEB("Query result: " << q.value(0).toString()); } // end of Debug return output; } /*! * \brief singleSelect Returns a single value from the database with a sqlite WHERE statement * \param table - Name of the table * \param column - Name of the column * \param value - Identifier for WHERE statement * \param match - enum sql::exactMatch or sql::partialMatch * \return QString */ QString db::singleSelect(QString column, QString table, QString checkColumn, QString value, sql::matchType match) { QString statement = "SELECT " + column + " FROM " + table + " WHERE " + checkColumn; QString result; switch (match) { case sql::exactMatch: statement += " = '" + value + QLatin1Char('\''); break; case sql::partialMatch: value.append(QLatin1Char('%')); value.prepend(QLatin1Char('%')); statement.append(" LIKE '" + value + QLatin1Char('\'')); break; } DEB(statement); QSqlQuery q(statement); q.exec(); if(!q.first()){ DEB("No result found. Check Query and Error."); DEB("Error: " << q.lastError().text()); return QString(); }else{ DEB("Success. Found a result."); result.append(q.value(0).toString()); if(q.next()){ DEB("More than one result in Database for your query"); } return result; } } /*! * \brief db::multiSelect Returns multiple values from the database with a sqlite WHERE statement * \param table - Name of the table * \param columns - QVector Names of the columns to be queried * \param value - Identifier for WHERE statement * \param checkColumn - column to match value to * \param match - enum sql::exactMatch or sql::partialMatch * \return QVector */ QVector db::multiSelect(QVector columns, QString table, QString checkColumn, QString value, sql::matchType match) { QString statement = "SELECT "; for(const auto& column : columns) { statement.append(column); if(column != columns.last()) { statement.append(QLatin1String(", ")); } } statement.append(" FROM " + table + " WHERE " + checkColumn); switch (match) { case sql::exactMatch: statement += " = '" + value + QLatin1Char('\''); break; case sql::partialMatch: value.append(QLatin1Char('%')); value.prepend(QLatin1Char('%')); statement.append(" LIKE '" + value + QLatin1Char('\'')); break; } DEB(statement); QSqlQuery q(statement); q.exec(); if(!q.first()){ DEB("No result found. Check Query and Error."); DEB("Error: " << q.lastError().text()); return QVector(); }else{ q.first(); q.previous(); QVector result; while (q.next()) { for(int i = 0; i < columns.size() ; i++) { result.append(q.value(i).toString()); } } return result; } } /*! * \brief db::multiSelect Returns a complete column(s) for a given table. Useful for creating * lists for QCompleter * \param column * \param table * \return */ QVector db::multiSelect(QVector columns, QString table) { QString statement = "SELECT "; for(const auto& column : columns) { statement.append(column); if(column != columns.last()) { statement.append(QLatin1String(", ")); } } statement.append(" FROM " + table); DEB(statement); QSqlQuery q(statement); q.exec(); if(!q.first()){ DEB("No result found. Check Query and Error."); DEB("Error: " << q.lastError().text()); return QVector(); }else{ q.first(); q.previous(); QVector result; while (q.next()) { for(int i = 0; i < columns.size() ; i++) { result.append(q.value(i).toString()); } } return result; } } /*! * \brief db::singleUpdate Updates a single value in the database. * Query format: UPDATE table SET column = value WHERE checkcolumn =/LIKE checkvalue * \param table Name of the table to be updated * \param column Name of the column to be updated * \param checkColumn Name of the column for WHERE statement * \param value The value to be set * \param checkvalue The value for the WHERE statement * \param match enum sql::exactMatch or sql::partialMatch * \return true on success, otherwise error messages in debug out */ bool db::singleUpdate(QString table, QString column, QString value, QString checkColumn, QString checkvalue, sql::matchType match) { QString statement = "UPDATE " + table; statement.append(QLatin1String(" SET ") + column + QLatin1String(" = '") + value); statement.append(QLatin1String("' WHERE ")); switch (match) { case sql::exactMatch: statement.append(checkColumn + " = '" + checkvalue + QLatin1Char('\'')); break; case sql::partialMatch: value.append(QLatin1Char('%')); value.prepend(QLatin1Char('%')); statement.append(checkColumn + " LIKE '" + checkvalue + QLatin1Char('\'')); break; } DEB(statement); QSqlQuery q(statement); q.exec(); QString error = q.lastError().text(); if(error.length() > 1) { DEB("Errors have occured: " << error); return false; }else { DEB("Success!"); return true; } } /*! * \brief db::deleteRow Deletes a single row from the database. * Query format: DELETE FROM table WHERE column =/LIKE value * \param table - Name of the table * \param column - Name of the column * \param value - Identifier for WHERE statement * \param match - enum sql::exactMatch or sql::partialMatch * \return true on success, otherwise error messages in debug out */ bool db::deleteRow(QString table, QString column, QString value, sql::matchType match) { QString statement = "DELETE FROM " + table + " WHERE "; statement.append(column); switch (match) { case sql::exactMatch: statement += " = '" + value + QLatin1Char('\''); break; case sql::partialMatch: value.append(QLatin1Char('%')); value.prepend(QLatin1Char('%')); statement.append(" LIKE '" + value + QLatin1Char('\'')); break; } DEB(statement); QSqlQuery q(statement); q.exec(); QString error = q.lastError().text(); if(error.length() > 1) { DEB("Errors have occured: " << error); return false; }else { DEB("Success!"); return true; } } /*! * \brief db::customQuery Can be used to send a complex query to the database. * \param query - the full sql query statement * \param returnValues - the number of expected return values * \return QVector of results */ QVector db::customQuery(QString query, int returnValues) { QSqlQuery q(query); DEB(query); q.exec(); if(!q.first()){ DEB("No result found. Check Query and Error."); DEB("Error: " << q.lastError().text()); return QVector(); }else{ q.first(); q.previous(); QVector result; while (q.next()) { for(int i = 0; i < returnValues ; i++) { result.append(q.value(i).toString()); } } return result; } } /*! * \brief db::getColumnNames Looks up column names of a given table * \param table name of the table in the database QVector db::getColumnNames(QString table) { QSqlDatabase db = QSqlDatabase::database("qt_sql_default_connection"); QVector columnNames; QSqlRecord fields = db.record(table); for(int i = 0; i < fields.count(); i++){ columnNames << fields.field(i).name(); } return columnNames; }*/ /*! * \brief db::update updates the database with the values contained in the object. * \return True on Success */ bool db::update() { //check prerequisites if(row_id == 0){ DEB("Invalid Row ID: " << row_id); return false; } if(data.isEmpty()){ DEB("Object Contains no data. Aborting."); return false; } //create query QString statement = "UPDATE " + table + " SET "; QMap::const_iterator i; for (i = data.constBegin(); i != data.constEnd(); ++i){ if(i.value()!=QString()){ statement += i.key()+QLatin1String("='")+i.value()+QLatin1String("', "); }else{DEB(i.key() << "is empty. skipping.");} } statement.chop(2); // Remove last comma statement.append(QLatin1String(" WHERE _rowid_=")+QString::number(row_id)); //execute query QSqlQuery q(statement); q.exec(); //check result. Upon success, error should be " " QString error = q.lastError().text(); if(error.length() < 2){ return true; }else{ DEB("Query Error: " << q.lastError().text()); return false; } } bool db::commit() { //check prerequisites if(row_id != 0){ DEB("Row ID already set. Unable to commit as new, try update() for existing entries: " << row_id); return false; } if(data.isEmpty()){ DEB("Object Contains no data. Aborting."); return false; } QString statement = "INSERT INTO " + table + QLatin1String(" ("); QMap::iterator i; for (i = data.begin(); i != data.end(); ++i){ statement += i.key() + QLatin1String(", "); } statement.chop(2); statement += QLatin1String(") VALUES ("); for (i = data.begin(); i != data.end(); ++i){ statement += QLatin1String("'") + i.value() + QLatin1String("', "); } statement.chop(2); statement += QLatin1String(")"); QSqlQuery q(statement); QString error = q.lastError().text(); if(error.length() < 2){ DEB("Entry successfully committed."); return true; }else{ DEB("Unable to commit. Query Error: " << q.lastError().text()); return false; } } //Debug void db::print() { QString v = "Object status:\t\033[38;2;0;255;0;48;2;0;0;0m VALID \033[0m\n"; QString nv = "Object status:\t\033[38;2;255;0;0;48;2;0;0;0m INVALID \033[0m\n"; QTextStream cout(stdout, QIODevice::WriteOnly); cout << "=========Database Object=========\n"; if(isValid){cout << v;}else{cout << nv;} cout << "Record from table: " << table << ", row: " << row_id << "\n"; cout << "=================================\n"; QMap::const_iterator i; for (i = data.constBegin(); i != data.constEnd(); ++i){ cout << i.key() << ": " << i.value() << "\n"; } } QString db::debug() { print(); return QString(); }