upgrade passed by sqlite [skip ci]

This commit is contained in:
Hamed Masafi 2019-02-16 17:06:38 +03:30
parent a0550b4e65
commit f3748241ff
19 changed files with 160 additions and 75 deletions

View File

@ -35,7 +35,7 @@ class ChangeLogTable : public Table
NUT_DECLARE_FIELD(QString, data, data, setData) NUT_DECLARE_FIELD(QString, data, data, setData)
NUT_DECLARE_FIELD(QString, version, version, setVersion) NUT_DECLARE_FIELD(int, version, version, setVersion)
public: public:
explicit ChangeLogTable(QObject *parentTableSet = Q_NULLPTR); explicit ChangeLogTable(QObject *parentTableSet = Q_NULLPTR);

View File

@ -173,16 +173,6 @@ bool DatabasePrivate::updateDatabase()
if (db.lastError().type() == QSqlError::NoError) { if (db.lastError().type() == QSqlError::NoError) {
q->databaseUpdated(last.version(), current.version()); q->databaseUpdated(last.version(), current.version());
//TODO: remove this
for (int i = 0; i < q->metaObject()->methodCount(); i++) {
QMetaMethod m = q->metaObject()->method(i);
if (m.name() == "update" + current.version()) {
m.invoke(q, Qt::DirectConnection,
Q_ARG(QString, current.version()));
break;
}
}
} else { } else {
qWarning("Unable update database, error = %s", qWarning("Unable update database, error = %s",
db.lastError().text().toLatin1().data()); db.lastError().text().toLatin1().data());
@ -220,7 +210,9 @@ bool DatabasePrivate::getCurrectScheema()
if (!nutClassInfoString(q->metaObject()->classInfo(i), if (!nutClassInfoString(q->metaObject()->classInfo(i),
type, name, value)) { type, name, value)) {
qDebug() << "No valid table in" << q->metaObject()->classInfo(i).value();
errorMessage = QString("No valid table in %1")
.arg(q->metaObject()->classInfo(i).value());
continue; continue;
} }
if (type == __nut_TABLE) { if (type == __nut_TABLE) {
@ -232,13 +224,17 @@ bool DatabasePrivate::getCurrectScheema()
if (!typeId) if (!typeId)
qFatal("The class %s is not registered with qt meta object", qPrintable(name)); qFatal("The class %s is not registered with qt meta object", qPrintable(name));
qDebug() << "Table found" << typeId;
TableModel *sch = new TableModel(typeId, value); TableModel *sch = new TableModel(typeId, value);
currentModel.append(sch); currentModel.append(sch);
} }
if (type == __nut_DB_VERSION) if (type == __nut_DB_VERSION) {
currentModel.setVersion(name); bool ok;
int version = value.toInt(&ok);
if (!ok)
qFatal("NUT_DB_VERSION macro accept version in format 'x'");
currentModel.setVersion(version);
/* TODO: remove /* TODO: remove
QStringList version QStringList version
@ -254,6 +250,7 @@ qDebug() << "Table found" << typeId;
if (!ok) if (!ok)
qFatal("NUT_DB_VERSION macro accept version in format 'x' or " qFatal("NUT_DB_VERSION macro accept version in format 'x' or "
"'x[.y]' only, and x,y must be integer values\n");*/ "'x[.y]' only, and x,y must be integer values\n");*/
}
} }
for (int i = 1; i < q->metaObject()->propertyCount(); i++) { for (int i = 1; i < q->metaObject()->propertyCount(); i++) {
@ -337,7 +334,7 @@ bool DatabasePrivate::putModelToDatabase()
/*current.remove(__CHANGE_LOG_TABLE_NAME)*/; /*current.remove(__CHANGE_LOG_TABLE_NAME)*/;
auto *changeLog = new ChangeLogTable(); auto *changeLog = new ChangeLogTable();
changeLog->setData(QJsonDocument(current.toJson()).toJson()); changeLog->setData(QJsonDocument(current.toJson()).toJson(QJsonDocument::Compact));
changeLog->setVersion(current.version()); changeLog->setVersion(current.version());
changeLogs->append(changeLog); changeLogs->append(changeLog);
q->saveChanges(); q->saveChanges();
@ -359,9 +356,10 @@ bool DatabasePrivate::putModelToDatabase()
void DatabasePrivate::createChangeLogs() void DatabasePrivate::createChangeLogs()
{ {
// currentModel.model("change_log") // currentModel.model("change_log")
QString diff = sqlGenertor->diff(nullptr, currentModel.tableByName("__change_log")); QStringList diff = sqlGenertor->diff(nullptr, currentModel.tableByName("__change_log"));
db.exec(diff); foreach (QString s, diff)
db.exec(s);
} }
/*! /*!
@ -530,7 +528,7 @@ QSqlDatabase Database::database()
return d->db; return d->db;
} }
void Database::databaseUpdated(QString oldVersion, QString newVersion) void Database::databaseUpdated(int oldVersion, int newVersion)
{ {
Q_UNUSED(oldVersion); Q_UNUSED(oldVersion);
Q_UNUSED(newVersion); Q_UNUSED(newVersion);

View File

@ -73,7 +73,7 @@ public:
protected: protected:
//remove minor version //remove minor version
virtual void databaseUpdated(QString oldVersion, QString newVersion); virtual void databaseUpdated(int oldVersion, int newVersion);
public slots: public slots:
void setDatabaseName(QString databaseName); void setDatabaseName(QString databaseName);

View File

@ -70,6 +70,8 @@ public:
QSet<TableSetBase *> tableSets; QSet<TableSetBase *> tableSets;
bool isDatabaseNew; bool isDatabaseNew;
QString errorMessage;
}; };
NUT_END_NAMESPACE NUT_END_NAMESPACE

View File

@ -31,13 +31,13 @@ QMap<QString, DatabaseModel*> DatabaseModel::_models;
#define NODE_VERSION "version" #define NODE_VERSION "version"
#define NODE_TABLES "tables" #define NODE_TABLES "tables"
DatabaseModel::DatabaseModel(const QString &name) : DatabaseModel::DatabaseModel(const QString &name) :
QList<TableModel*>(), _databaseClassName(name), _version(QString()) QList<TableModel*>(), _databaseClassName(name), _version(0)
{ {
_models.insert(name, this); _models.insert(name, this);
} }
DatabaseModel::DatabaseModel(const DatabaseModel &other) : DatabaseModel::DatabaseModel(const DatabaseModel &other) :
QList<TableModel*>(other), _version(QString()) QList<TableModel*>(other), _version(0)
{ {
} }
@ -45,7 +45,7 @@ DatabaseModel::DatabaseModel(const DatabaseModel &other) :
DatabaseModel::DatabaseModel(const QJsonObject &json) : DatabaseModel::DatabaseModel(const QJsonObject &json) :
QList<TableModel*>() QList<TableModel*>()
{ {
setVersion(json.value(NODE_VERSION).toString()); setVersion(json.value(NODE_VERSION).toInt());
QJsonObject tables = json.value(NODE_TABLES).toObject(); QJsonObject tables = json.value(NODE_TABLES).toObject();
foreach (QString key, tables.keys()) { foreach (QString key, tables.keys()) {
@ -171,7 +171,7 @@ DatabaseModel DatabaseModel::fromJson(QJsonObject &json)
{ {
DatabaseModel model; DatabaseModel model;
model.setVersion(json.value(NODE_VERSION).toString()); model.setVersion(json.value(NODE_VERSION).toInt());
QJsonObject tables = json.value(NODE_TABLES).toObject(); QJsonObject tables = json.value(NODE_TABLES).toObject();
foreach (QString key, tables.keys()) { foreach (QString key, tables.keys()) {
@ -184,12 +184,12 @@ DatabaseModel DatabaseModel::fromJson(QJsonObject &json)
return model; return model;
} }
QString DatabaseModel::version() const int DatabaseModel::version() const
{ {
return _version; return _version;
} }
void DatabaseModel::setVersion(QString version) void DatabaseModel::setVersion(int version)
{ {
_version = version; _version = version;
} }

View File

@ -36,7 +36,7 @@ struct RelationModel;
class DatabaseModel : public QList<TableModel *> class DatabaseModel : public QList<TableModel *>
{ {
QString _databaseClassName; QString _databaseClassName;
QString _version; int _version;
static QMap<QString, DatabaseModel *> _models; static QMap<QString, DatabaseModel *> _models;
public: public:
@ -61,8 +61,8 @@ public:
QJsonObject toJson() const; QJsonObject toJson() const;
operator QJsonObject(); operator QJsonObject();
QString version() const; int version() const;
void setVersion(QString version); void setVersion(int version);
bool remove(const QString &tableName); bool remove(const QString &tableName);

View File

@ -171,13 +171,9 @@ QString PostgreSqlGenerator::fieldType(FieldModel *field)
return "TEXT"; return "TEXT";
default: default:
qDebug() << "Type for " << (int)field->type << field->type << "(" << QMetaType::typeName(field->type) << ")" << "nut supported";
dbType = QString(); dbType = QString();
} }
if(field->type == (unsigned)QMetaType::type("Nut::DbGeography"))
dbType = "GEOGRAPHY";
return dbType; return dbType;
} }

View File

@ -132,7 +132,7 @@ QStringList SqlGeneratorBase::diff(const DatabaseModel &lastModel,
foreach (TableModel *table, unionModel) { foreach (TableModel *table, unionModel) {
TableModel *oldTable = lastModel.tableByName(table->name()); TableModel *oldTable = lastModel.tableByName(table->name());
TableModel *newTable = newModel.tableByName(table->name()); TableModel *newTable = newModel.tableByName(table->name());
QString sql = diff(oldTable, newTable); QStringList sql = diff(oldTable, newTable);
if (!sql.isEmpty()) if (!sql.isEmpty())
ret << sql; ret << sql;
// QString sqlRel = diffRelation(oldTable, newTable); // QString sqlRel = diffRelation(oldTable, newTable);
@ -148,7 +148,7 @@ QString SqlGeneratorBase::diff(FieldModel *oldField, FieldModel *newField)
QString sql = QString(); QString sql = QString();
if (oldField && newField) if (oldField && newField)
if (*oldField == *newField) if (*oldField == *newField)
return QString(); return sql;
if (!newField) { if (!newField) {
sql = "DROP COLUMN " + oldField->name; sql = "DROP COLUMN " + oldField->name;
@ -162,14 +162,14 @@ QString SqlGeneratorBase::diff(FieldModel *oldField, FieldModel *newField)
return sql; return sql;
} }
QString SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable) QStringList SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable)
{ {
if (oldTable && newTable) if (oldTable && newTable)
if (*oldTable == *newTable) if (*oldTable == *newTable)
return QString(); return QStringList();
if (!newTable) if (!newTable)
return "DROP TABLE " + oldTable->name(); return QStringList() << ("DROP TABLE " + oldTable->name());
QList<QString> fieldNames; QList<QString> fieldNames;
QList<QString> relations; QList<QString> relations;
@ -197,12 +197,12 @@ QString SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable)
FieldModel *oldField = oldTable->field(fieldName); FieldModel *oldField = oldTable->field(fieldName);
QString buffer = diff(oldField, newField); QString buffer = diff(oldField, newField);
if (!buffer.isNull()) if (!buffer.isEmpty())
columnSql << buffer; columnSql << buffer;
} else { } else {
QString declare = fieldDeclare(newField); QString declare = fieldDeclare(newField);
if (declare.isEmpty()) if (declare.isEmpty())
return declare; return QStringList() << declare;
columnSql << declare; columnSql << declare;
} }
} }
@ -231,14 +231,16 @@ QString SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable)
sql = QString("CREATE TABLE %1 \n(%2)") sql = QString("CREATE TABLE %1 \n(%2)")
.arg(newTable->name(), columnSql.join(",\n")); .arg(newTable->name(), columnSql.join(",\n"));
} }
return sql; return QStringList() << sql;
} }
QString SqlGeneratorBase::diffRelation(TableModel *oldTable, TableModel *newTable) QStringList SqlGeneratorBase::diffRelation(TableModel *oldTable, TableModel *newTable)
{ {
QStringList ret;
if (!newTable) if (!newTable)
return QString(); return ret;
QList<QString> relations; QList<QString> relations;
@ -258,20 +260,21 @@ QString SqlGeneratorBase::diffRelation(TableModel *oldTable, TableModel *newTabl
if (oldTable) if (oldTable)
oldRelation = oldTable->foregionKeyByField(fieldName); oldRelation = oldTable->foregionKeyByField(fieldName);
QString buffer = diff(oldRelation, newRelation); QStringList buffer = diff(oldRelation, newRelation);
if (!buffer.isNull()) if (!buffer.isEmpty())
columnSql << buffer; columnSql << buffer.at(0);
} }
if (columnSql.count()) if (columnSql.count())
return "ALTER TABLE " + newTable->name() + "\n" ret.append("ALTER TABLE " + newTable->name() + "\n"
+ columnSql.join(",\n"); + columnSql.join(",\n"));
return QString(); return ret;
} }
QString SqlGeneratorBase::diff(RelationModel *oldRel, RelationModel *newRel) QStringList SqlGeneratorBase::diff(RelationModel *oldRel, RelationModel *newRel)
{ {
QStringList ret;
/* /*
CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID) CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID)
REFERENCES Persons(PersonID) REFERENCES Persons(PersonID)
@ -285,19 +288,19 @@ QString SqlGeneratorBase::diff(RelationModel *oldRel, RelationModel *newRel)
.arg(newRelation->foreignColumn); .arg(newRelation->foreignColumn);
*/ */
if (!oldRel) if (!oldRel)
return QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) " ret.append(QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) "
"REFERENCES %2(%3)") "REFERENCES %2(%3)")
.arg(newRel->localColumn, newRel->masterTable->name(), .arg(newRel->localColumn, newRel->masterTable->name(),
newRel->foreignColumn); newRel->foreignColumn));
if (!newRel) if (!newRel)
return QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) " ret.append(QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) "
"REFERENCES %2(%3)") "REFERENCES %2(%3)")
.arg(oldRel->localColumn, oldRel->masterTable->name(), .arg(oldRel->localColumn, oldRel->masterTable->name(),
oldRel->foreignColumn); oldRel->foreignColumn));
// if (*oldRel == *newRel) // if (*oldRel == *newRel)
return QString(); return ret;
} }
QString SqlGeneratorBase::join(const QString &mainTable, QString SqlGeneratorBase::join(const QString &mainTable,

View File

@ -84,9 +84,9 @@ public:
virtual QStringList diff(const DatabaseModel &lastModel, const DatabaseModel &newModel); virtual QStringList diff(const DatabaseModel &lastModel, const DatabaseModel &newModel);
virtual QString diff(FieldModel *oldField, FieldModel *newField); virtual QString diff(FieldModel *oldField, FieldModel *newField);
virtual QString diff(TableModel *oldTable, TableModel *newTable); virtual QStringList diff(TableModel *oldTable, TableModel *newTable);
virtual QString diffRelation(TableModel *oldTable, TableModel *newTable); virtual QStringList diffRelation(TableModel *oldTable, TableModel *newTable);
virtual QString diff(RelationModel *oldRel, RelationModel *newRel); virtual QStringList diff(RelationModel *oldRel, RelationModel *newRel);
virtual QString join(const QString &mainTable, virtual QString join(const QString &mainTable,
const QList<RelationModel*> &list, const QList<RelationModel*> &list,

View File

@ -95,6 +95,79 @@ QString SqliteGenerator::fieldType(FieldModel *field)
} }
} }
QStringList SqliteGenerator::diff(TableModel *oldTable, TableModel *newTable)
{
QStringList ret;
if (oldTable && newTable)
if (*oldTable == *newTable)
return ret;
QStringList newTableSql = SqlGeneratorBase::diff(nullptr, newTable);
if (!newTable)
return QStringList() << "DROP TABLE " + oldTable->name();
if (!oldTable)
return newTableSql;
QList<QString> fieldNames;
QList<QString> relations;
foreach (FieldModel *f, oldTable->fields())
if (!fieldNames.contains(f->name))
fieldNames.append(f->name);
foreach (RelationModel *r, oldTable->foregionKeys())
if (!relations.contains(r->localColumn))
relations.append(r->localColumn);
foreach (FieldModel *f, newTable->fields())
if (!fieldNames.contains(f->name))
fieldNames.append(f->name);
foreach (RelationModel *r, newTable->foregionKeys())
if (!relations.contains(r->localColumn))
relations.append(r->localColumn);
QString columns;
foreach (FieldModel *f, oldTable->fields()) {
if (!newTable->field(f->name))
continue;
if (!columns.isEmpty())
columns.append(", ");
columns.append(f->name);
}
/*
ALTER TABLE sampleTable RENAME TO sqlitestudio_temp_table;
CREATE TABLE sampleTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
t BIGINT,
m CHAR
);
INSERT INTO sampleTable (
id,
t,
m
)
SELECT id,
t,
m
FROM sqlitestudio_temp_table;
DROP TABLE sqlitestudio_temp_table;
*/
ret.append("ALTER TABLE " + newTable->name() + " RENAME TO sqlitestudio_temp_table;");
ret.append(newTableSql);
ret.append(QString("INSERT INTO %1 ( %2 ) SELECT %2 FROM sqlitestudio_temp_table;")
.arg(newTable->name(), columns));
ret.append("DROP TABLE sqlitestudio_temp_table;");
return ret;
}
void SqliteGenerator::appendSkipTake(QString &sql, int skip, int take) void SqliteGenerator::appendSkipTake(QString &sql, int skip, int take)
{ {
if (take != -1 && skip != -1) if (take != -1 && skip != -1)

View File

@ -31,11 +31,12 @@ class SqliteGenerator : public SqlGeneratorBase
public: public:
explicit SqliteGenerator(Database *parent = nullptr); explicit SqliteGenerator(Database *parent = nullptr);
QString fieldType(FieldModel *field); QString fieldType(FieldModel *field) override;
void appendSkipTake(QString &sql, int skip, int take); void appendSkipTake(QString &sql, int skip, int take) override;
QString primaryKeyConstraint(const TableModel *table) const; QString primaryKeyConstraint(const TableModel *table) const override;
QStringList diff(TableModel *oldTable, TableModel *newTable) override;
}; };
NUT_END_NAMESPACE NUT_END_NAMESPACE

View File

@ -146,7 +146,7 @@ QString SqlServerGenerator::diff(FieldModel *oldField, FieldModel *newField)
QString sql = QString(); QString sql = QString();
if (oldField && newField) if (oldField && newField)
if (*oldField == *newField) if (*oldField == *newField)
return QString(); return sql;
if (!newField) { if (!newField) {
sql = "DROP COLUMN " + oldField->name; sql = "DROP COLUMN " + oldField->name;

View File

@ -97,14 +97,12 @@ AbstractFieldPhrase::AbstractFieldPhrase(const char *className,
const char *fieldName) const char *fieldName)
:data(new PhraseData(className, fieldName)) :data(new PhraseData(className, fieldName))
{ {
qDebug() <<"AbstractFieldPhrase created"<<className<<fieldName;
} }
AbstractFieldPhrase::AbstractFieldPhrase(const AbstractFieldPhrase &other) AbstractFieldPhrase::AbstractFieldPhrase(const AbstractFieldPhrase &other)
{ {
data = other.data; data = other.data;
data->parents++; data->parents++;
qDebug() <<"Copy ctor"<<other.data->toString()<<other.data->parents;
} }
AbstractFieldPhrase::AbstractFieldPhrase(AbstractFieldPhrase &&other) AbstractFieldPhrase::AbstractFieldPhrase(AbstractFieldPhrase &&other)
@ -358,7 +356,6 @@ ConditionalPhrase::ConditionalPhrase() : data(nullptr)
ConditionalPhrase::ConditionalPhrase(const ConditionalPhrase &other) ConditionalPhrase::ConditionalPhrase(const ConditionalPhrase &other)
{ {
qDebug() << "************* ctor called:";
data = other.data; data = other.data;
data->parents++; data->parents++;
// const_cast<ConditionalPhrase&>(other).data = 0; // const_cast<ConditionalPhrase&>(other).data = 0;
@ -367,7 +364,6 @@ ConditionalPhrase::ConditionalPhrase(const ConditionalPhrase &other)
#ifdef Q_COMPILER_RVALUE_REFS #ifdef Q_COMPILER_RVALUE_REFS
ConditionalPhrase::ConditionalPhrase(const ConditionalPhrase &&other) ConditionalPhrase::ConditionalPhrase(const ConditionalPhrase &&other)
{ {
qDebug() << "************* ctor called:";
this->data = qMove(other.data); this->data = qMove(other.data);
} }
#endif #endif

View File

@ -39,9 +39,11 @@ NUT_BEGIN_NAMESPACE
* This should be fixed to v1.2 * This should be fixed to v1.2
*/ */
Table::Table(QObject *parent) : QObject(parent), myModel(nullptr), Table::Table(QObject *parent) : QObject(parent),
_status(NewCreated), _parentTableSet(nullptr) _status(NewCreated), _parentTableSet(nullptr)
{ } {
myModel = TableModel::findByClassName(metaObject()->className());
}
void Table::add(TableSetBase *t) void Table::add(TableSetBase *t)
{ {

View File

@ -22,11 +22,11 @@ JoinTest::JoinTest(QObject *parent) : QObject(parent)
void JoinTest::initTestCase() void JoinTest::initTestCase()
{ {
qDebug() << "User type id:" << qRegisterMetaType<User*>(); REGISTER(User);
qDebug() << "Post type id:" << qRegisterMetaType<Post*>(); REGISTER(Post);
qDebug() << "Comment type id:" << qRegisterMetaType<Comment*>(); REGISTER(Comment);
qDebug() << "Score type id:" << qRegisterMetaType<Score*>(); REGISTER(Score);
qDebug() << "DB type id:" << qRegisterMetaType<WeblogDatabase*>(); REGISTER(WeblogDatabase);
db.setDriver(DRIVER); db.setDriver(DRIVER);
db.setHostName(HOST); db.setHostName(HOST);

View File

@ -19,8 +19,8 @@ UuidTest::UuidTest(QObject *parent) : QObject(parent)
void UuidTest::initTestCase() void UuidTest::initTestCase()
{ {
qDebug() << "Test type id:" << qRegisterMetaType<Test*>(); REGISTER(Test);
qDebug() << "DB type id:" << qRegisterMetaType<TestDatabase*>(); REGISTER(TestDatabase);
QFile::remove(DATABASE); QFile::remove(DATABASE);

View File

@ -10,6 +10,8 @@ class Table3 : public Nut::Table
NUT_PRIMARY_AUTO_INCREMENT(id) NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId) NUT_DECLARE_FIELD(int, id, id, setId)
NUT_DECLARE_FIELD(QString, grade, grade, setGrade)
public: public:
Q_INVOKABLE Table3(QObject *parent = Q_NULLPTR); Q_INVOKABLE Table3(QObject *parent = Q_NULLPTR);

View File

@ -7,6 +7,7 @@
#include "table1.h" #include "table1.h"
#include "table2.h" #include "table2.h"
#include "table3.h" #include "table3.h"
#include "query.h"
#include "tst_upgrades.h" #include "tst_upgrades.h"
#include "consts.h" #include "consts.h"
@ -55,6 +56,12 @@ void Upgrades::version2()
DB2 db; DB2 db;
initDb(db); initDb(db);
QTEST_ASSERT(db.open()); QTEST_ASSERT(db.open());
Table2 t;
t.setStr("0");
db.sampleTable()->append(&t);
db.saveChanges();
id = t.id();
} }
void Upgrades::version3() void Upgrades::version3()
@ -62,6 +69,10 @@ void Upgrades::version3()
DB3 db; DB3 db;
initDb(db); initDb(db);
QTEST_ASSERT(db.open()); QTEST_ASSERT(db.open());
auto t = db.sampleTable()->query()
->first();
QTEST_ASSERT(id == t->id());
} }

View File

@ -12,6 +12,7 @@ class Upgrades : public QObject
void initDb(Nut::Database &db); void initDb(Nut::Database &db);
int id;
public: public:
Upgrades(); Upgrades();
~Upgrades(); ~Upgrades();