511 lines
14 KiB
C++
Executable File
511 lines
14 KiB
C++
Executable File
#include "QueryThread.h"
|
|
#include "DataModel.h"
|
|
#include "AddModel.h"
|
|
|
|
#include <QSqlDatabase>
|
|
#include <QSqlError>
|
|
#include <QSqlField>
|
|
#include <QSqlQuery>
|
|
#include <QSqlRecord>
|
|
#include <QDebug>
|
|
|
|
// uncomment next line to unarm the delete method - delete method does actually not delete content
|
|
//#define UNARMED
|
|
const char browserName[] = "SQLBrowser";
|
|
const char driverName[] = "QSQLITE";
|
|
|
|
|
|
|
|
QueryWorker::QueryWorker()
|
|
: m_dy(false)
|
|
, m_idle(true)
|
|
{
|
|
//qWarning() << "QueryWorker";
|
|
//test.dummy();
|
|
}
|
|
|
|
|
|
QueryWorker::~QueryWorker()
|
|
{
|
|
//qWarning() << "~QueryWorker";
|
|
m_idle = true;
|
|
m_sqlDB.close();
|
|
//QSqlDatabase::removeDatabase(BROWSER_NAME);
|
|
}
|
|
|
|
|
|
void QueryWorker::slotGetTableList()
|
|
{
|
|
m_idle = false;
|
|
|
|
if (m_sqlDB.isOpen())
|
|
{
|
|
QStringList tables = m_sqlDB.tables();
|
|
tables.append(m_sqlDB.tables(QSql::Views));
|
|
tables.sort();
|
|
|
|
emit signalNewTableList(tables);
|
|
}
|
|
else
|
|
{
|
|
//qWarning() << "mep...";
|
|
}
|
|
|
|
m_idle = true;
|
|
}
|
|
|
|
|
|
void QueryWorker::slotConnectIt(const LoginData &loginData)
|
|
{
|
|
m_idle = false;
|
|
|
|
// SQLite
|
|
if (!loginData.filename.isEmpty())
|
|
{
|
|
m_sqlDB = QSqlDatabase::addDatabase(QLatin1String(driverName), QLatin1String(browserName));
|
|
m_sqlDB.setDatabaseName(loginData.filename);
|
|
}
|
|
else // DBMS
|
|
{
|
|
// CONNECT & TEST
|
|
m_sqlDB = QSqlDatabase::addDatabase(loginData.driver, QLatin1String(browserName));
|
|
m_sqlDB.setDatabaseName(loginData.database);
|
|
m_sqlDB.setHostName(loginData.hostname);
|
|
m_sqlDB.setPort(loginData.port);
|
|
m_sqlDB.setUserName(loginData.username);
|
|
m_sqlDB.setPassword(loginData.password);
|
|
}
|
|
|
|
if (!m_sqlDB.open())
|
|
{
|
|
emit signalConnectResult(m_sqlDB.lastError().text());
|
|
}
|
|
else
|
|
{
|
|
emit signalConnectResult(QString()); // connected!
|
|
}
|
|
|
|
m_idle = true;
|
|
}
|
|
|
|
|
|
void QueryWorker::slotDeleteLines(Query* caller, const QList<QModelIndex> &selectedRows, DataModel* dataModel)
|
|
{
|
|
m_idle = false;
|
|
|
|
if (dataModel->getTableName().isEmpty())
|
|
{
|
|
emit signalDeleteLinesResult(caller, 0, QStringLiteral("This is a custom query"), QList<QModelIndex>());
|
|
m_idle = true;
|
|
return;
|
|
}
|
|
|
|
QString deleteStatement(QStringLiteral("DELETE FROM %1 WHERE %2"));
|
|
|
|
if (!selectedRows.count())
|
|
{
|
|
emit signalDeleteLinesResult(caller, 0, QStringLiteral("No line(s) selected to delete"), QList<QModelIndex>());
|
|
m_idle = true;
|
|
return;
|
|
}
|
|
|
|
// DELETE FROM table_name WHERE (x1=1 AND x2=2) OR (x1=2 AND x=3)
|
|
|
|
QStringList rows; // (x1=1 AND x2=2) (x1=2 AND x2=3) - join with "AND" !
|
|
|
|
rows.reserve(selectedRows.size());
|
|
for (int rowIterator = 0; rowIterator < selectedRows.count(); rowIterator++)
|
|
{
|
|
QStringList whereStatements;
|
|
int columnCount = dataModel->columnCount();
|
|
whereStatements.reserve(columnCount);
|
|
int row = selectedRows.at(rowIterator).row();
|
|
|
|
//qWarning() << "row" << row;
|
|
|
|
for (int column = 0; column < columnCount; column++)
|
|
{
|
|
//qWarning() << "col" << column;
|
|
|
|
QVariant var = dataModel->data(dataModel->index(row, column), Qt::DisplayRole);
|
|
QString value = valueToSqlString(var.toString(), dataModel->getHeaderList(), column);
|
|
|
|
QStringList whereStatement;
|
|
whereStatement.reserve(2);
|
|
|
|
whereStatement.append(dataModel->headerData(column, Qt::Horizontal, Qt::DisplayRole).toString()); // column name
|
|
whereStatement.append(value);
|
|
|
|
if (value == QStringLiteral("NULL"))
|
|
whereStatements.append(whereStatement.join(QStringLiteral(" IS "))); // >>> column_name = %value% <<<
|
|
else
|
|
whereStatements.append(whereStatement.join(QStringLiteral("="))); // >>> column_name IS NULL <<<
|
|
}
|
|
|
|
QString rowText(QStringLiteral("(%1)").arg(whereStatements.join(QStringLiteral(" AND "))));
|
|
rows.append(rowText); // >>> column_name1 = 'value1' AND column_name2 = 'value2' <<<
|
|
}
|
|
|
|
|
|
deleteStatement = deleteStatement.arg(dataModel->getTableName(), rows.join(QStringLiteral(" OR ")));
|
|
|
|
qWarning() << "OKAY! my statement = >>>" << deleteStatement;
|
|
|
|
|
|
|
|
int rowsAffected = -1;
|
|
emit signalDeleteLinesResult(caller, rowsAffected,
|
|
#ifdef UNARMED
|
|
QString(),
|
|
#else
|
|
executeQuery(&rowsAffected, deleteStatement),
|
|
#endif
|
|
selectedRows);
|
|
|
|
m_idle = true;
|
|
}
|
|
|
|
|
|
QString QueryWorker::selectQuery(const QString &queryString, DataModel* dataModel)
|
|
{
|
|
m_idle = false;
|
|
|
|
QString result;
|
|
|
|
QSqlQuery q(m_sqlDB);
|
|
|
|
if (q.exec(queryString) && !m_dy) // execute query first..
|
|
{
|
|
// header
|
|
//HeaderList headerList;
|
|
|
|
const auto record = q.record();
|
|
int querySize = q.size(); // aka rows
|
|
int recordCount = record.count(); // aka columns
|
|
|
|
int statusStep = querySize / 100;
|
|
int statusCurrent = 0; // NOT in percent! (maximum = querySize)
|
|
int statusPercent = 0; // 0 - 100
|
|
|
|
// TODO: let's ignore the pk autoincrement first...
|
|
//qWarning() << "primaryIndex" << m_sqlDB.primaryIndex(dataModel->getTableName());
|
|
//q.record().field(i).setAutoValue(true);
|
|
|
|
|
|
for (int i = 0; i < recordCount; i++)
|
|
{
|
|
dataModel->addColumn(record.fieldName(i), record.field(i).type());
|
|
|
|
if (m_dy) break;
|
|
}
|
|
|
|
|
|
// data
|
|
//DataList dataList;
|
|
dataModel->dataListReserve(querySize); // speedup
|
|
|
|
Data data;
|
|
data.reserve(recordCount);
|
|
while (q.next() && !m_dy)
|
|
{
|
|
if (statusCurrent >= statusStep)
|
|
{
|
|
statusPercent++;
|
|
statusCurrent = 0;
|
|
emit signalSelectQueryStatus(statusPercent);
|
|
}
|
|
statusCurrent++;
|
|
|
|
data.clear();
|
|
|
|
for (int i = 0; i < recordCount; i++)
|
|
data.append(q.value(i));
|
|
|
|
dataModel->addLine(data);
|
|
}
|
|
|
|
|
|
// done reading, create model
|
|
//DataModel* dataModel = 0;
|
|
if (!m_dy)
|
|
{
|
|
// resultHeaderList = headerList;
|
|
// resultDataList = dataList;
|
|
|
|
connect(this, &QueryWorker::signalSwapModelData,
|
|
dataModel, &DataModel::slotSwapLists);
|
|
|
|
emit signalSwapModelData(); // single shot.... for threading
|
|
|
|
disconnect(this, &QueryWorker::signalSwapModelData,
|
|
dataModel, &DataModel::slotSwapLists);
|
|
|
|
//qWarning() << "QueryThread: headerList.count()" << headerList->count();
|
|
//qWarning() << "QueryThread: dataList.count()" << dataList->count();
|
|
}
|
|
}
|
|
else // query failed
|
|
{
|
|
if (!m_dy) // only an error when not dying
|
|
{
|
|
result = /*
|
|
emit signalSelectQueryResult(*/q.lastError().text()/*, 0)*/; // TODO: respect qt4 return invalid pointer
|
|
}
|
|
}
|
|
|
|
m_idle = true;
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
void QueryWorker::slotSetData(Query* caller, DataModel* model, const QModelIndex &index, const QVariant &value)
|
|
{
|
|
// TODO: use executeQuery here!
|
|
|
|
m_idle = false;
|
|
|
|
QString queryString(QStringLiteral("UPDATE %1 SET %2=%3 WHERE %4"));
|
|
|
|
int row = index.row();
|
|
|
|
/* %1 */ QString tableName = model->getTableName();
|
|
|
|
if (tableName.isEmpty())
|
|
{
|
|
emit signalSetDataResult(caller, QStringLiteral("This is a custom query"));
|
|
m_idle = true;
|
|
return;
|
|
}
|
|
|
|
/* %2 */ QString columnToChange = model->headerData(index.column(), Qt::Horizontal).toString();
|
|
|
|
const HeaderList &headerList = model->getHeaderList();
|
|
|
|
/* %3 */ const QString &valueForColumn = valueToSqlString(value, headerList, index.column()); // cannot get from model, as setData retured FALSE, which resets the change
|
|
|
|
/* %4 */ QString allColumnValuesInRow;
|
|
QStringList allColumnValuesInRowList;
|
|
|
|
allColumnValuesInRowList.reserve(model->columnCount());
|
|
|
|
QString condition;
|
|
|
|
for (int i = 0; i < model->columnCount(); i++)
|
|
{
|
|
QString columnName = model->headerData(i, Qt::Horizontal).toString();
|
|
QString columnValue = valueToSqlString(model->data(model->index(row, i)).toString(), headerList, i);
|
|
|
|
// nur für Spalte mit Namen 'id'
|
|
if (columnName.compare(QStringLiteral("id"), Qt::CaseInsensitive) == 0)
|
|
condition = QStringLiteral("%1=%2").arg(columnName, columnValue);
|
|
|
|
QString statement(QStringLiteral("%1=%2").arg(columnName, columnValue));
|
|
if (model->data(model->index(row, i)).isNull())
|
|
statement = QStringLiteral("%1 IS NULL").arg(columnName);
|
|
|
|
allColumnValuesInRowList.append(statement);
|
|
}
|
|
allColumnValuesInRow = allColumnValuesInRowList.join(QStringLiteral(" AND "));
|
|
if (condition.isEmpty())
|
|
condition = allColumnValuesInRow;
|
|
|
|
queryString = queryString.arg(tableName, columnToChange, valueForColumn, condition);
|
|
|
|
// qCritical() << "queryString =" << queryString;
|
|
|
|
QSqlQuery q(m_sqlDB);
|
|
if (q.exec(queryString) && !m_dy) // execute set data query
|
|
{
|
|
// worked? then change value in model
|
|
|
|
// signalSetDataResult(caller, QStringLiteral("Num affected rows: %1").arg(q.numRowsAffected()));
|
|
|
|
auto con = connect(this, &QueryWorker::signalSetDataInModel,
|
|
model, &DataModel::slotSetData);
|
|
|
|
if (value.toString().isEmpty())
|
|
emit signalSetDataInModel(index, QVariant()); // single shot.... for threading
|
|
else
|
|
emit signalSetDataInModel(index, value); // single shot.... for threading
|
|
|
|
disconnect(con);
|
|
|
|
emit signalSetDataResult(caller, QString()); // empty string = success
|
|
}
|
|
else
|
|
{
|
|
emit signalSetDataResult(caller, q.lastError().text()); // error
|
|
}
|
|
|
|
m_idle = true;
|
|
}
|
|
|
|
|
|
void QueryWorker::slotTableQuery(Query* caller, DataModel *model)
|
|
{
|
|
// no need to unidle here, as selectQuery does it!
|
|
|
|
//qWarning() << "QueryWorker: model" << model;
|
|
|
|
if (model->getTableName().isEmpty())
|
|
{
|
|
emit signalTableQueryResult(caller, QStringLiteral("This is a custom query"));
|
|
return;
|
|
}
|
|
|
|
QString queryString(QStringLiteral("SELECT * FROM %1").arg(model->getTableName()));
|
|
|
|
emit signalTableQueryResult(caller, selectQuery(queryString, model));
|
|
}
|
|
|
|
|
|
void QueryWorker::slotCustomQuery(Query* caller, const QString &queryString, DataModel *model)
|
|
{
|
|
// no need to unidle here, as selectQuery does it!
|
|
|
|
model->setTableName(QString()); // set empty table name, as this is not a table query anymore!
|
|
emit signalCustomQueryResult(caller, selectQuery(queryString, model));
|
|
}
|
|
|
|
|
|
void QueryWorker::slotAddLines(Query* caller, AddModel* addModel)
|
|
{
|
|
if (!addModel) // no lines to add
|
|
{
|
|
emit signalAddLinesResult(caller, 0, QString());
|
|
return;
|
|
}
|
|
|
|
// no need to unidle here, as executeQuery does it!
|
|
|
|
QString addQuery(QStringLiteral("INSERT INTO %1 VALUES %2"));
|
|
|
|
QStringList rows;
|
|
rows.reserve(addModel->rowCount());
|
|
|
|
for (int row = 0; row < addModel->rowCount() - 1 /* ignore empty add-line */; row++)
|
|
{
|
|
QStringList columns;
|
|
int columnCount = addModel->columnCount();
|
|
columns.reserve(columnCount);
|
|
|
|
for (int col = 0; col < columnCount; col++)
|
|
{
|
|
//qWarning() << "col" << col << "type" << addModel->getColumnType(col);
|
|
|
|
columns << valueToSqlString(addModel->data(addModel->index(row,col)), addModel->getHeaderList(), col);
|
|
|
|
////#warning implement IS NULL !
|
|
// if (addModel->getColumnType(col) == QVariant::Int) // get sql type
|
|
// {
|
|
// columns << addModel->data(addModel->index(row, col)).toString();
|
|
// }
|
|
// else // string
|
|
// {
|
|
// QString str = "'%1'";
|
|
// str = str.arg(addModel->data(addModel->index(row, col)).toString());
|
|
// columns << str;
|
|
// }
|
|
}
|
|
|
|
const QString rowString(QLatin1Char('(') + columns.join(QLatin1Char(',')) + QLatin1Char(')'));
|
|
rows << rowString;
|
|
}
|
|
|
|
|
|
addQuery = addQuery.arg(addModel->getTableName(), rows.join(QLatin1Char(',')));
|
|
|
|
int affectedRows = -1;
|
|
emit signalAddLinesResult(caller, affectedRows, executeQuery(&affectedRows, addQuery));
|
|
|
|
qWarning() << "OKAY! add query = " << addQuery;
|
|
}
|
|
|
|
|
|
//QString QueryWorker::valueFromSqlString(const QString& val, const HeaderList &headerList, const int col)
|
|
//{
|
|
// // value comes from sql database and must be made 'visual'
|
|
|
|
// QVariant::Type type = headerList.at(col).second; // get column type from model
|
|
|
|
// //qWarning() << "QueryWorker::variantToSqlString: type =" << type;
|
|
|
|
// if (val.isEmpty()) // if val is empty, return empty sql type "NULL"
|
|
// {
|
|
// return "NULL";
|
|
// }
|
|
|
|
// if (type == QVariant::String) // col is string type
|
|
// {
|
|
// return "'" + val + "'";
|
|
// }
|
|
// else // col is ANY int type
|
|
// {
|
|
// return val;
|
|
// }
|
|
//}
|
|
|
|
|
|
QString QueryWorker::valueToSqlString(const QVariant& val, const HeaderList &headerList, const int col)
|
|
{
|
|
// value must be converted to sql value
|
|
|
|
QVariant::Type type = headerList.at(col).second; // get column type from model
|
|
|
|
//qWarning() << "QueryWorker::valueToSqlString: val" << val << "type" << type << "("<<type<<")";
|
|
|
|
if (val.isNull() || val.toString().isEmpty()) // if val is empty, return empty sql type "NULL"
|
|
return QStringLiteral("NULL");
|
|
|
|
//if (type == QVariant::String) // col is string type
|
|
switch (type)
|
|
{
|
|
case QVariant::Int:
|
|
return QString::number(val.toInt());
|
|
case QVariant::UInt:
|
|
return QString::number(val.toUInt());
|
|
case QVariant::LongLong:
|
|
return QString::number(val.toLongLong());
|
|
case QVariant::ULongLong:
|
|
return QString::number(val.toULongLong());
|
|
case QVariant::Double:
|
|
return QString::number(val.toDouble());
|
|
|
|
default: // any other than numeric type
|
|
break;
|
|
}
|
|
|
|
return QStringLiteral("'%1'").arg(val.toString());
|
|
}
|
|
|
|
|
|
QString QueryWorker::executeQuery(int* affectedRows, const QString &queryString)
|
|
{
|
|
m_idle = false;
|
|
|
|
QString result;
|
|
|
|
QSqlQuery q(m_sqlDB);
|
|
|
|
if (q.exec(queryString))
|
|
{
|
|
qWarning() << "executeQuery: success: numRowsAffected" << q.numRowsAffected();
|
|
*affectedRows= q.numRowsAffected();
|
|
}
|
|
else // error
|
|
{
|
|
*affectedRows = 0;
|
|
result = q.lastError().text(); // error (no rows affected)
|
|
|
|
// if (result.isEmpty())
|
|
// {
|
|
// result = "UNKNOWN ERROR";
|
|
// }
|
|
}
|
|
|
|
m_idle = true;
|
|
|
|
return result;
|
|
}
|