/*
*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 "adatabase.h"
namespace experimental {
ADataBase* ADataBase::instance = nullptr;
ADataBase* ADataBase::getInstance()
{
if(!instance)
instance = new ADataBase();
return instance;
}
bool ADataBase::connect()
{
const QString driver("QSQLITE");
if (!QSqlDatabase::isDriverAvailable(driver))
return false;
QDir directory("data");
QString databaseLocation = directory.filePath("logbook.db");
QSqlDatabase db = QSqlDatabase::addDatabase(driver);
db.setDatabaseName(databaseLocation);
if (!db.open())
return false;
DEB("Database connection established.");
// Enable foreign key restrictions
QSqlQuery query("PRAGMA foreign_keys = ON;");
tableNames = db.tables();
QStringList columnNames;
for (const auto &table : tableNames) {
columnNames.clear();
QSqlRecord fields = db.record(table);
for (int i = 0; i < fields.count(); i++) {
columnNames.append(fields.field(i).name());
tableColumns.insert(table, columnNames);
}
}
DEB("Database Tables: " << tableNames);
return true;
}
void ADataBase::disconnect()
{
auto db = ADataBase::database();
db.close();
db.removeDatabase(db.connectionName());
DEB("Database connection closed.");
}
QSqlDatabase ADataBase::database()
{
return QSqlDatabase::database("qt_sql_default_connection");
}
bool ADataBase::commit(AEntry entry)
{
if (exists(entry)) {
return update(entry);
} else {
return insert(entry);
}
}
bool ADataBase::remove(AEntry entry)
{
if (!exists(entry)) {
DEB("Error: Database entry not found.");
lastError = "Database Error: Database entry not found.";
return false;
}
QString statement = "DELETE FROM " + entry.getPosition().tableName +
" WHERE ROWID=" + QString::number(entry.getPosition().rowId);
QSqlQuery query(statement);
if (query.lastError().type() == QSqlError::NoError)
{
DEB("Entry " << entry.getPosition().tableName << entry.getPosition().rowId << " removed.");
emit dataBaseUpdated();
lastError = QString();
return true;
} else {
DEB("Unable to delete.");
DEB("Query: " << statement);
DEB("Query Error: " << query.lastError().text());
lastError = query.lastError().text();
return false;
}
}
bool ADataBase::removeMany(QList data_position_list)
{
int errorCount = 0;
QSqlQuery query;
query.prepare("BEGIN EXCLUSIVE TRANSACTION");
query.exec();
for (const auto data_position : data_position_list) {
if (!exists(data_position)) {
lastError = "Database Error: Database entry not found.";
errorCount++;
}
QString statement = "DELETE FROM " + data_position.first +
" WHERE ROWID=" + QString::number(data_position.second);
query.prepare(statement);
query.exec();
if (!(query.lastError().type() == QSqlError::NoError))
errorCount++;
}
if (errorCount == 0) {
query.prepare("COMMIT");
query.exec();
if(query.lastError().type() == QSqlError::NoError) {
emit dataBaseUpdated();
lastError = QString();
return true;
} else {
lastError = "Transaction unsuccessful (Interrupted). Error count: " + QString::number(errorCount);
return false;
}
} else {
query.prepare("ROLLBACK");
query.exec();
lastError = "Transaction unsuccessful (no changes have been made). Error count: " + QString::number(errorCount);
return false;
}
}
bool ADataBase::exists(AEntry entry)
{
if(entry.getPosition().second == 0)
return false;
//Check database for row id
QString statement = "SELECT COUNT(*) FROM " + entry.getPosition().tableName +
" WHERE ROWID=" + QString::number(entry.getPosition().rowId);
QSqlQuery query;
query.prepare(statement);
query.setForwardOnly(true);
query.exec();
//this returns either 1 or 0 since row ids are unique
if (!query.isActive()) {
lastError = query.lastError().text();
DEB("Query Error: " << query.lastError().text() << statement);
return false;
}
query.next();
int rowId = query.value(0).toInt();
if (rowId) {
DEB("Entry " << entry.getPosition() << " exists.");
return true;
} else {
DEB("Database entry not found.");
lastError = "Database Error: Database entry not found.";
return false;
}
}
bool ADataBase::exists(DataPosition data_position)
{
if(data_position.second == 0)
return false;
//Check database for row id
QString statement = "SELECT COUNT(*) FROM " + data_position.first +
" WHERE ROWID=" + QString::number(data_position.second);
QSqlQuery query;
query.prepare(statement);
query.setForwardOnly(true);
query.exec();
//this returns either 1 or 0 since row ids are unique
if (!query.isActive()) {
lastError = query.lastError().text();
DEB("Query Error: " << query.lastError().text() << statement);
}
query.next();
int rowId = query.value(0).toInt();
if (rowId) {
DEB("Entry exists at DataPosition: " << data_position);
return true;
} else {
DEB("No entry exists at DataPosition: " << data_position);
lastError = "Database Error: Database entry not found.";
return false;
}
}
bool ADataBase::update(AEntry updated_entry)
{
auto data = updated_entry.getData();
QString statement = "UPDATE " + updated_entry.getPosition().tableName + " SET ";
for (auto i = data.constBegin(); i != data.constEnd(); ++i) {
if (i.value() == QString()) {
statement += i.key() + QLatin1String("=NULL") + QLatin1String(", ");
} else {
statement += i.key() + QLatin1String("=\"") + i.value() + QLatin1String("\", ");
}
}
statement.chop(2); // Remove last comma
statement.append(QLatin1String(" WHERE ROWID=") + QString::number(updated_entry.getPosition().rowId));
DEB("UPDATE QUERY: " << statement);
QSqlQuery query(statement);
if (query.lastError().type() == QSqlError::NoError)
{
DEB("Entry successfully committed.");
emit dataBaseUpdated();
lastError = QString();
return true;
} else {
DEB("Unable to commit.");
DEB("Query: " << statement);
DEB("Query Error: " << query.lastError().text());
lastError = query.lastError().text();
return false;
}
}
bool ADataBase::insert(AEntry new_entry)
{
auto data = new_entry.getData();
DEB("Inserting...");
QString statement = "INSERT INTO " + new_entry.getPosition().tableName + 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) {
if (i.value() == "") {
statement += QLatin1String("NULL, ");
} else {
statement += QLatin1String("\"") + i.value() + QLatin1String("\", ");
}
}
statement.chop(2);
statement += QLatin1String(")");
DEB(statement);
QSqlQuery query(statement);
//check result.
if (query.lastError().type() == QSqlError::NoError)
{
DEB("Entry successfully committed.");
emit dataBaseUpdated();
lastError = QString();
return true;
} else {
DEB("Unable to commit.");
DEB("Query: " << statement);
DEB("Query Error: " << query.lastError().text());
lastError = query.lastError().text();
return false;
}
}
TableData ADataBase::getEntryData(DataPosition data_position)
{
// check table exists
if (!tableNames.contains(data_position.first)) {
DEB(data_position.first << " not a table in the database. Unable to retreive Entry data.");
return TableData();
}
//Check Database for rowId
QString statement = "SELECT COUNT(*) FROM " + data_position.first
+ " WHERE ROWID=" + QString::number(data_position.second);
QSqlQuery check_query;
check_query.prepare(statement);
check_query.setForwardOnly(true);
check_query.exec();
if (check_query.lastError().type() != QSqlError::NoError) {
DEB("SQL error: " << check_query.lastError().text());
DEB("Statement: " << statement);
lastError = check_query.lastError().text();
return TableData();
}
check_query.next();
if (check_query.value(0).toInt() == 0) {
DEB("No Entry found for row id: " << data_position.second );
lastError = "Database Error: Database entry not found.";
return TableData();
}
// Retreive TableData
DEB("Retreiving data for row id: " << data_position.second);
statement = "SELECT * FROM " + data_position.first
+ " WHERE ROWID=" + QString::number(data_position.second);
QSqlQuery select_query;
select_query.prepare(statement);
select_query.setForwardOnly(true);
select_query.exec();
if (select_query.lastError().type() != QSqlError::NoError) {
DEB("SQL error: " << select_query.lastError().text());
DEB("Statement: " << statement);
lastError = select_query.lastError().text();
return TableData();
}
select_query.next();
TableData entry_data;
for (const auto &column : tableColumns.value(data_position.first)) {
entry_data.insert(column, select_query.value(column).toString());
}
return entry_data;
}
AEntry ADataBase::getEntry(DataPosition data_position)
{
AEntry entry(data_position);
entry.setData(getEntryData(data_position));
return entry;
}
APilotEntry ADataBase::getPilotEntry(RowId row_id)
{
APilotEntry pilot_entry(row_id);
pilot_entry.setData(getEntryData(pilot_entry.getPosition()));
return pilot_entry;
}
ATailEntry ADataBase::getTailEntry(RowId row_id)
{
ATailEntry tail_entry(row_id);
tail_entry.setData(getEntryData(tail_entry.getPosition()));
return tail_entry;
}
AAircraftEntry ADataBase::getAircraftEntry(RowId row_id)
{
AAircraftEntry aircraft_entry(row_id);
aircraft_entry.setData(getEntryData(aircraft_entry.getPosition()));
return aircraft_entry;
}
AFlightEntry ADataBase::getFlightEntry(RowId row_id)
{
AFlightEntry flight_entry(row_id);
flight_entry.setData(getEntryData(flight_entry.getPosition()));
return flight_entry;
}
const QStringList ADataBase::getCompletionList(ADataBase::DatabaseTarget target)
{
QString statement;
switch (target) {
case pilots:
statement.append("SELECT piclastname||\", \"||picfirstname FROM pilots");
break;
case aircraft:
statement.append("SELECT make||\" \"||model FROM aircraft WHERE model IS NOT NULL "
"UNION "
"SELECT make||\" \"||model||\"-\"||variant FROM aircraft WHERE variant IS NOT NULL");
break;
case airport_identifier_all:
statement.append("SELECT icao FROM airports UNION SELECT iata FROM airports");
break;
case registrations:
statement.append("SELECT registration FROM tails");
break;
case companies:
statement.append("SELECT company FROM pilots");
break;
default:
DEB("Not a valid completer target for this function.");
return QStringList();
}
QSqlQuery query;
query.prepare(statement);
query.setForwardOnly(true);
query.exec();
if(!query.isActive()) {
lastError = query.lastError().text();
return QStringList();
}
QStringList completer_list;
while (query.next())
completer_list.append(query.value(0).toString());
completer_list.sort();
completer_list.removeAll(QString(""));
completer_list.removeDuplicates();
return completer_list;
}
const QMap ADataBase::getIdMap(ADataBase::DatabaseTarget target)
{
QString statement;
switch (target) {
case pilots:
statement.append("SELECT ROWID, piclastname||\", \"||picfirstname FROM pilots");
break;
case aircraft:
statement.append("SELECT ROWID, make||\" \"||model FROM aircraft WHERE model IS NOT NULL "
"UNION "
"SELECT ROWID, make||\" \"||model||\"-\"||variant FROM aircraft WHERE variant IS NOT NULL");
break;
case airport_identifier_icao:
statement.append("SELECT ROWID, icao FROM airports");
break;
case airport_identifier_iata:
statement.append("SELECT ROWID, iata FROM airports WHERE iata NOT NULL");
break;
case airport_names:
statement.append("SELECT ROWID, name FROM airports");
break;
case tails:
statement.append("SELECT ROWID, registration FROM tails");
break;
default:
DEB("Not a valid completer target for this function.");
return QMap();
}
auto id_map = QMap();
auto query = QSqlQuery(statement);
if (!query.isActive()) {
DEB("No result found. Check Query and Error.");
DEB("Query: " << statement);
DEB("Error: " << query.lastError().text());
lastError = query.lastError().text();
return QMap();
} else {
QVector query_result;
while (query.next()) {
id_map.insert(query.value(1).toString(), query.value(0).toInt());
}
return id_map;
}
}
int ADataBase::getLastEntry(ADataBase::DatabaseTarget target)
{
QString statement = "SELECT MAX(ROWID) FROM ";
switch (target) {
case pilots:
statement.append("pilots");
break;
case aircraft:
statement.append("aircraft");
break;
case tails:
statement.append("tails");
break;
default:
DEB("Not a valid completer target for this function.");
return 0;
}
auto query = QSqlQuery(statement);
if (query.first()) {
return query.value(0).toInt();
} else {
lastError = "Database Error: Database entry not found.";
DEB("No entry found.");
return 0;
}
}
QVector ADataBase::customQuery(QString statement, int return_values)
{
QSqlQuery query(statement);
query.exec();
if (!query.first()) {
DEB("No result found. Check Query and Error.");
DEB("Error: " << query.lastError().text());
DEB("Statement: " << statement);
lastError = query.lastError().text();
return QVector();
} else {
query.first();
query.previous();
QVector result;
while (query.next()) {
for (int i = 0; i < return_values ; i++) {
result.append(query.value(i).toString());
}
}
emit dataBaseUpdated();
lastError = QString();
return result;
}
}
ADataBase* aDB() { return ADataBase::getInstance(); }
}