Nut/src/database.cpp

475 lines
13 KiB
C++
Raw Normal View History

2016-05-12 14:08:58 +08:00
/**************************************************************************
**
** This file is part of Nut project.
** https://github.com/HamedMasafi/Nut
**
** Nut is free software: you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** Nut 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 Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with Nut. If not, see <http://www.gnu.org/licenses/>.
**
**************************************************************************/
#include <QtCore/QMetaProperty>
#include <QtCore/QDebug>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlError>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlResult>
#include "database.h"
#include "table.h"
#include "tableset.h"
#include "database_p.h"
2017-02-01 18:01:21 +08:00
#include "defines.h"
2016-05-21 16:09:03 +08:00
#include "tablemodel.h"
2016-05-12 14:08:58 +08:00
#include "postgresqlgenerator.h"
#include "mysqlgenerator.h"
#include "sqlitegenerator.h"
2016-05-21 16:09:03 +08:00
#include "sqlservergenerator.h"
2016-05-12 14:08:58 +08:00
#include "query.h"
#include <iostream>
#include <cstdarg>
2017-02-01 18:01:21 +08:00
#define __CHANGE_LOG_TABLE_NAME "__change_logs"
NUT_BEGIN_NAMESPACE
int DatabasePrivate::lastId = 0;
2016-05-12 14:08:58 +08:00
DatabasePrivate::DatabasePrivate(Database *parent) : q_ptr(parent)
{
}
bool DatabasePrivate::open()
{
2017-02-01 18:01:21 +08:00
Q_Q(Database);
2016-05-12 14:08:58 +08:00
getCurrectScheema();
2017-02-01 18:01:21 +08:00
connectionName = q->metaObject()->className() + QString::number(DatabasePrivate::lastId);
2016-05-12 14:08:58 +08:00
db = QSqlDatabase::addDatabase(driver, connectionName);
db.setHostName(hostName);
db.setDatabaseName(databaseName);
db.setUserName(userName);
db.setPassword(password);
bool ok = db.open();
if(!ok){
2017-02-01 18:01:21 +08:00
qWarning("Could not connect to database, error = %s", db.lastError().text().toLocal8Bit().data());
2016-05-12 14:08:58 +08:00
2016-05-21 16:09:03 +08:00
if(db.lastError().text().contains("database \"" + databaseName + "\" does not exist")
2016-06-05 20:22:26 +08:00
|| db.lastError().text().contains("Cannot open database")
|| db.lastError().text().contains("Unknown database '" + databaseName + "'")){
2016-05-21 16:09:03 +08:00
db.setDatabaseName(sqlGenertor->masterDatabaseName(databaseName));
2016-05-12 14:08:58 +08:00
ok = db.open();
2016-06-05 20:22:26 +08:00
qDebug("Creating database");
2016-05-12 14:08:58 +08:00
if(ok){
db.exec("CREATE DATABASE " + databaseName);
db.close();
2016-05-21 16:09:03 +08:00
if(db.lastError().type() != QSqlError::NoError)
2017-02-01 18:01:21 +08:00
qWarning("Creating database error: %s", db.lastError().text().toLatin1().data());
2016-05-21 16:09:03 +08:00
2016-05-12 14:08:58 +08:00
return open();
}else{
2017-02-01 18:01:21 +08:00
qWarning("Unknown error detecting change logs, %s", db.lastError().text().toLatin1().data());
2016-05-12 14:08:58 +08:00
}
}
return false;
}
return updateDatabase();
}
bool DatabasePrivate::updateDatabase()
{
2016-06-05 20:22:26 +08:00
Q_Q(Database);
2016-05-12 14:08:58 +08:00
DatabaseModel last = getLastScheema();
2016-05-21 16:09:03 +08:00
DatabaseModel current = currentModel;
2016-05-12 14:08:58 +08:00
2016-05-21 16:09:03 +08:00
if(last == current){
2016-06-05 20:22:26 +08:00
qDebug("Databse is up-to-date");
2016-05-12 14:08:58 +08:00
return true;
}
2016-05-21 16:09:03 +08:00
if(!last.count())
2016-06-05 20:22:26 +08:00
qDebug("Databse is new");
2016-05-21 16:09:03 +08:00
else
2016-06-05 20:22:26 +08:00
qDebug("Databse is changed");
2016-05-12 14:08:58 +08:00
2016-05-21 16:09:03 +08:00
QStringList sql = sqlGenertor->diff(last, current);
2016-05-12 14:08:58 +08:00
db.transaction();
foreach (QString s, sql){
db.exec(s);
2016-06-05 20:22:26 +08:00
if(db.lastError().type() != QSqlError::NoError)
2017-02-01 18:01:21 +08:00
qWarning("Error executing sql command, %s", db.lastError().text().toLatin1().data());
2016-05-12 14:08:58 +08:00
}
bool ok = db.commit();
if(ok){
storeScheemaInDB();
2016-06-05 20:22:26 +08:00
q->databaseUpdated(last.versionMajor(), last.versionMinor(), current.versionMajor(), current.versionMinor());
QString versionText = QString::number(current.versionMajor()) + "_" + QString::number(current.versionMinor());
for(int i = 0; i < q->metaObject()->methodCount(); i++){
QMetaMethod m = q->metaObject()->method(i);
if(m.name() == "update" + versionText){
m.invoke(q, Qt::DirectConnection,
Q_ARG(int, current.versionMajor()),
Q_ARG(int, current.versionMinor()));
break;
}
}
2016-05-12 14:08:58 +08:00
}else{
2017-02-01 18:01:21 +08:00
qWarning("Unable update database, error = %s", db.lastError().text().toLatin1().data());
2016-05-12 14:08:58 +08:00
}
return ok;
}
2017-02-01 18:01:21 +08:00
void DatabasePrivate::getCurrectScheema()
2016-05-12 14:08:58 +08:00
{
Q_Q(Database);
tables.clear();
2016-05-21 16:09:03 +08:00
//TODO: change logs must not be in model
2016-05-12 14:08:58 +08:00
int changeLogTypeId = qRegisterMetaType<ChangeLogTable*>();
2017-02-01 18:01:21 +08:00
currentModel.append(new TableModel(changeLogTypeId, __CHANGE_LOG_TABLE_NAME));
tables.insert(ChangeLogTable::staticMetaObject.className(), __CHANGE_LOG_TABLE_NAME);
changeLogs = new TableSet<ChangeLogTable>(q);
2016-05-12 14:08:58 +08:00
for(int i = 0; i < q->metaObject()->classInfoCount(); i++){
QMetaClassInfo ci = q->metaObject()->classInfo(i);
2016-06-05 20:22:26 +08:00
QString ciName = QString(ci.name()).replace(__nut_NAME_PERFIX, "").replace("\"", "");
2017-02-01 18:01:21 +08:00
2016-05-21 16:09:03 +08:00
if(ciName.startsWith(__nut_TABLE))
2016-06-05 20:22:26 +08:00
tables.insert(ciName.split(" ").at(1), ci.value());
2016-05-21 16:09:03 +08:00
if(ciName == __nut_DB_VERSION){
2016-06-05 20:22:26 +08:00
QStringList version = QString(ci.value()).replace("\"", "").split('.');
2016-05-21 16:09:03 +08:00
bool ok = false;
if(version.length() == 1){
currentModel.setVersionMajor(version.at(0).toInt(&ok));
} else if(version.length() == 2){
currentModel.setVersionMajor(version.at(0).toInt(&ok));
currentModel.setVersionMinor(version.at(1).toInt(&ok));
}
if(!ok)
2016-06-05 20:22:26 +08:00
qFatal("NUT_DB_VERSION macro accept version in format 'x' or 'x[.y]' only, and x,y must be integer values\n");
2016-05-21 16:09:03 +08:00
}
2016-05-12 14:08:58 +08:00
}
for(int i = 1; i < q->metaObject()->propertyCount(); i++){
QMetaProperty tableProperty = q->metaObject()->property(i);
2016-06-05 20:22:26 +08:00
int typeId = QMetaType::type(tableProperty.typeName());
2016-05-12 14:08:58 +08:00
2017-02-01 18:01:21 +08:00
qDebug() << tables.values().contains(tableProperty.name()) << typeId;
2016-05-12 14:08:58 +08:00
if(tables.values().contains(tableProperty.name()) && typeId >= QVariant::UserType){
2016-05-21 16:09:03 +08:00
TableModel *sch = new TableModel(typeId, tableProperty.name());
2016-05-12 14:08:58 +08:00
currentModel.append(sch);
}
}
2016-05-21 16:09:03 +08:00
foreach (TableModel *sch, currentModel)
foreach (RelationModel *fk, sch->foregionKeys())
fk->table = currentModel.modelByClass(fk->className);
2016-05-12 14:08:58 +08:00
}
DatabaseModel DatabasePrivate::getLastScheema()
{
2017-02-01 18:01:21 +08:00
Q_Q(Database);
// ChangeLogTable *u = q->_change_logs()->createQuery()->orderBy("id", "desc")->first();
2016-05-21 16:09:03 +08:00
ChangeLogTable *u = changeLogs->createQuery()->orderBy("id", "desc")->first();
2016-05-12 14:08:58 +08:00
DatabaseModel ret;
if(u){
2016-05-21 16:09:03 +08:00
QJsonObject json = QJsonDocument::fromJson(QByteArray(u->data().toLocal8Bit().data())).object();
2016-05-12 14:08:58 +08:00
foreach (QString key, json.keys()) {
2016-05-21 16:09:03 +08:00
TableModel *sch = new TableModel(json.value(key).toObject(), key);
2016-05-12 14:08:58 +08:00
ret.append(sch);
}
2016-05-21 16:09:03 +08:00
u->deleteLater();
2016-05-12 14:08:58 +08:00
}
return ret;
2017-02-01 18:01:21 +08:00
// QSqlQuery query = q->exec("select * from __change_logs order by id desc limit 1");
// DatabaseModel ret;
// if(query.next()){
// QJsonObject json = QJsonDocument::fromJson(query.value("data").toByteArray()).object();
// foreach (QString key, json.keys()) {
// TableModel *sch = new TableModel(json.value(key).toObject(), key);
// ret.append(sch);
// }
// }
//qDebug() << "ret=" <<ret;
// return ret;
2016-05-12 14:08:58 +08:00
}
bool DatabasePrivate::storeScheemaInDB()
{
2016-05-21 16:09:03 +08:00
Q_Q(Database);
2017-02-01 18:01:21 +08:00
DatabaseModel current = currentModel;
/*current.remove(__CHANGE_LOG_TABLE_NAME)*/;
2016-05-12 14:08:58 +08:00
ChangeLogTable *changeLog = new ChangeLogTable();
2017-02-01 18:01:21 +08:00
changeLog->setData(QJsonDocument(current.toJson()).toJson());
changeLog->setVersionMajor(current.versionMajor());
changeLog->setVersionMinor(current.versionMinor());
2016-05-21 16:09:03 +08:00
changeLogs->append(changeLog);
q->saveChanges();
changeLog->deleteLater();
return true;
2017-02-01 18:01:21 +08:00
// QSqlQuery query(db);
// query.prepare("insert into __change_logs (data) values (:data)");
// query.bindValue(":data", QString(QJsonDocument(currentModel.toJson()).toJson()));
// bool ret = query.exec();
// if(query.lastError().type() != QSqlError::NoError)
// qWarning(QString("storeScheemaInDB" + query.lastError().text()).toLatin1().data());
2016-05-21 16:09:03 +08:00
// return ret;
2016-05-12 14:08:58 +08:00
}
void DatabasePrivate::createChangeLogs()
{
2017-02-01 18:01:21 +08:00
// currentModel.model("change_log")
QString diff = sqlGenertor->diff(0, currentModel.model("__change_log"));
2016-05-12 14:08:58 +08:00
db.exec(diff);
}
2016-06-05 20:22:26 +08:00
/*!
* \class Database
* \brief Database class
*/
2016-05-12 14:08:58 +08:00
Database::Database(QObject *parent) : QObject(parent), d_ptr(new DatabasePrivate(this))
{
Q_D(Database);
2017-02-01 18:01:21 +08:00
//d->changeLogs->sett
DatabasePrivate::lastId++;
// m__change_logs = new TableSet<ChangeLogTable>(this);
}
Database::Database(const Database &other, QObject *parent) : QObject(parent), d_ptr(new DatabasePrivate(this))
{
Q_D(Database);
DatabasePrivate::lastId++;
setDriver(other.driver());
setHostName(other.hostName());
setDatabaseName(other.databaseName());
setUserName(other.userName());
setPassword(other.password());
// m__change_logs = new TableSet<ChangeLogTable>(this);
2016-05-12 14:08:58 +08:00
}
QString Database::databaseName() const
{
Q_D(const Database);
return d->databaseName;
}
QString Database::hostName() const
{
Q_D(const Database);
return d->hostName;
}
int Database::port() const
{
Q_D(const Database);
return d->port;
}
QString Database::userName() const
{
Q_D(const Database);
return d->userName;
}
QString Database::password() const
{
Q_D(const Database);
return d->password;
}
2016-06-10 16:14:31 +08:00
/*!
* \brief Database::connectionName
* \return Connection name of current Database \l QSqlDatabase::connectionName
*/
2016-05-12 14:08:58 +08:00
QString Database::connectionName() const
{
Q_D(const Database);
return d->connectionName;
}
QString Database::driver() const
{
Q_D(const Database);
return d->driver;
}
2016-06-05 20:22:26 +08:00
/*!
* \brief Database::model
* \return The model of this database
*/
2016-05-12 14:08:58 +08:00
DatabaseModel Database::model() const
{
Q_D(const Database);
return d->currentModel;
}
QString Database::tableName(QString className)
{
Q_D(Database);
return d->tables[className];
}
void Database::setDatabaseName(QString databaseName)
{
Q_D(Database);
d->databaseName = databaseName;
}
void Database::setHostName(QString hostName)
{
Q_D(Database);
d->hostName = hostName;
}
void Database::setPort(int port)
{
Q_D(Database);
d->port = port;
}
void Database::setUserName(QString username)
{
Q_D(Database);
d->userName = username;
}
void Database::setPassword(QString password)
{
Q_D(Database);
d->password = password;
}
void Database::setConnectionName(QString connectionName)
{
Q_D(Database);
d->connectionName = connectionName;
}
void Database::setDriver(QString driver)
{
Q_D(Database);
d->driver = driver;
}
SqlGeneratorBase *Database::sqlGenertor() const
{
Q_D(const Database);
return d->sqlGenertor;
}
2016-06-05 20:22:26 +08:00
void Database::databaseUpdated(int oldMajor, int oldMinor, int newMajor, int newMinor)
{
Q_UNUSED(oldMajor);
Q_UNUSED(oldMinor);
Q_UNUSED(newMajor);
Q_UNUSED(newMinor);
}
2016-05-12 14:08:58 +08:00
bool Database::open()
{
Q_D(Database);
2016-05-21 16:09:03 +08:00
if(d->driver == "QPSQL" || d->driver == "QPSQL7")
2016-05-24 14:47:37 +08:00
d->sqlGenertor = new PostgreSqlGenerator(this);
2016-05-21 16:09:03 +08:00
else if (d->driver == "QMYSQL" || d->driver == "QMYSQL3")
2016-05-24 14:47:37 +08:00
d->sqlGenertor = new MySqlGenerator(this);
2016-05-21 16:09:03 +08:00
else if (d->driver == "QSQLITE" || d->driver == "QSQLITE3")
2016-05-24 14:47:37 +08:00
d->sqlGenertor = new SqliteGenerator(this);
2016-05-21 16:09:03 +08:00
else if(d->driver == "QODBC" || d->driver == "QODBC3"){
QString driverName = QString::null;
QStringList parts = d->databaseName.toLower().split(';');
foreach (QString p, parts)
if(p.trimmed().startsWith("driver="))
2017-02-01 18:01:21 +08:00
driverName = p.split('=').at(1).toLower().trimmed();
2016-05-21 16:09:03 +08:00
if(driverName == "{sql server}")
2016-05-24 14:47:37 +08:00
d->sqlGenertor = new SqlServerGenerator(this);
2017-02-01 18:01:21 +08:00
//TODO: add ODBC driver for mysql, postgres, ...
2016-05-21 16:09:03 +08:00
}
2016-05-12 14:08:58 +08:00
if(!d->sqlGenertor){
2017-02-01 18:01:21 +08:00
qWarning("Sql generator for driver %s not found", driver().toLatin1().constData());
2016-05-12 14:08:58 +08:00
return false;
}else{
return d->open();
}
}
2016-06-05 20:22:26 +08:00
void Database::close()
{
Q_D(Database);
d->db.close();
}
2016-05-12 14:08:58 +08:00
QSqlQuery Database::exec(QString sql)
{
Q_D(Database);
2017-02-01 18:01:21 +08:00
2016-05-12 14:08:58 +08:00
QSqlQuery q = d->db.exec(sql);
if(d->db.lastError().type() != QSqlError::NoError)
2017-02-01 18:01:21 +08:00
qWarning("Error executing sql command: %s", d->db.lastError().text().toLatin1().data());
2016-05-12 14:08:58 +08:00
return q;
}
void Database::add(TableSetBase *t)
{
tableSets.insert(t);
}
void Database::saveChanges()
{
foreach(TableSetBase *ts, tableSets)
ts->save(this);
}
2016-05-21 16:09:03 +08:00
void Database::cleanUp()
{
foreach(TableSetBase *ts, tableSets)
ts->clearChilds();
}
2017-02-01 18:01:21 +08:00
NUT_END_NAMESPACE