wip: detect and apply foreign keys

This commit is contained in:
Hamed Masafi 2018-02-24 19:13:15 +03:30
parent 9c0df27eea
commit fa21efde1b
8 changed files with 231 additions and 52 deletions

View File

@ -141,7 +141,7 @@ bool DatabasePrivate::updateDatabase()
qDebug("Databse is changed"); qDebug("Databse is changed");
QStringList sql = sqlGenertor->diff(last, current); QStringList sql = sqlGenertor->diff(last, current);
qDebug()<<"database Sql =\n"<<sql;
db.transaction(); db.transaction();
foreach (QString s, sql) { foreach (QString s, sql) {
db.exec(s); db.exec(s);

View File

@ -24,6 +24,7 @@
#define __NAME "name" #define __NAME "name"
#define __TYPE "type" #define __TYPE "type"
#define __FIELDS "fields" #define __FIELDS "fields"
#define __FOREIGN_KEYS "foreign_keys"
#define __nut_FIELD "field" #define __nut_FIELD "field"
#define __nut_DB_VERSION "database_version" #define __nut_DB_VERSION "database_version"

View File

@ -70,7 +70,7 @@ QString SqlGeneratorBase::masterDatabaseName(QString databaseName)
QString SqlGeneratorBase::createTable(TableModel *table) QString SqlGeneratorBase::createTable(TableModel *table)
{ {
Q_UNUSED(table); Q_UNUSED(table)
return ""; return "";
} }
@ -114,6 +114,13 @@ QString SqlGeneratorBase::fieldDeclare(FieldModel *field)
return field->name + " " + fieldType(field) + (field->notNull ? " NOT NULL" : ""); return field->name + " " + fieldType(field) + (field->notNull ? " NOT NULL" : "");
} }
QString SqlGeneratorBase::relationDeclare(const RelationModel *relation)
{
return QString("FOREIGN KEY (FK_%1) REFERENCES %2(%1)")
.arg(relation->localColumn)
.arg(relation->slaveTable->name());
}
QStringList SqlGeneratorBase::diff(DatabaseModel lastModel, QStringList SqlGeneratorBase::diff(DatabaseModel lastModel,
DatabaseModel newModel) DatabaseModel newModel)
{ {
@ -125,9 +132,11 @@ QStringList SqlGeneratorBase::diff(DatabaseModel lastModel,
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); QString sql = diff(oldTable, newTable);
if (!sql.isEmpty()) if (!sql.isEmpty())
ret << sql; ret << sql;
// QString sqlRel = diffRelation(oldTable, newTable);
// if (!sqlRel.isEmpty())
// ret << sqlRel;
} }
return ret; return ret;
@ -161,14 +170,24 @@ QString SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable)
if (!newTable) if (!newTable)
return "DROP TABLE " + oldTable->name(); return "DROP TABLE " + oldTable->name();
QSet<QString> fieldNames; QList<QString> fieldNames;
QList<QString> relations;
if (oldTable) if (oldTable) {
foreach (FieldModel *f, oldTable->fields()) foreach (FieldModel *f, oldTable->fields())
fieldNames.insert(f->name); 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()) foreach (FieldModel *f, newTable->fields())
fieldNames.insert(f->name); if (!fieldNames.contains(f->name))
fieldNames.append(f->name);
foreach (RelationModel *r, newTable->foregionKeys())
if (!relations.contains(r->localColumn))
relations.append(r->localColumn);
QStringList columnSql; QStringList columnSql;
foreach (QString fieldName, fieldNames) { foreach (QString fieldName, fieldNames) {
@ -183,22 +202,104 @@ QString SqlGeneratorBase::diff(TableModel *oldTable, TableModel *newTable)
columnSql << fieldDeclare(newField); columnSql << fieldDeclare(newField);
} }
} }
// foreach (QString fieldName, relations) {
// RelationModel *newRelation = newTable->foregionKeyByField(fieldName);
// if (oldTable) {
// RelationModel *oldRelation = oldTable->foregionKeyByField(fieldName);
// QString buffer = diff(oldRelation, newRelation);
// if (!buffer.isNull())
// columnSql << buffer;
// } else {
// columnSql << relationDeclare(newRelation);
// }
// }
QString sql; QString sql;
if (oldTable) { if (oldTable) {
sql = QString("ALTER TABLE %1 \n%2").arg(newTable->name()).arg( sql = QString("ALTER TABLE %1 \n%2")
columnSql.join(",\n")); .arg(newTable->name())
.arg(columnSql.join(",\n"));
} else { } else {
if (!newTable->primaryKey().isNull()) if (!newTable->primaryKey().isNull())
columnSql << QString("CONSTRAINT pk_%1 PRIMARY KEY (%2)") columnSql << QString("CONSTRAINT pk_%1 PRIMARY KEY (%2)")
.arg(newTable->name()) .arg(newTable->name())
.arg(newTable->primaryKey()); .arg(newTable->primaryKey());
sql = QString("CREATE TABLE %1 \n(%2)").arg(newTable->name()).arg( sql = QString("CREATE TABLE %1 \n(%2)")
columnSql.join(",\n")); .arg(newTable->name())
.arg(columnSql.join(",\n"));
} }
return sql; return sql;
} }
QString SqlGeneratorBase::diffRelation(TableModel *oldTable, TableModel *newTable)
{
if (!newTable)
return "";
QList<QString> relations;
if (oldTable)
foreach (RelationModel *r, oldTable->foregionKeys())
if (!relations.contains(r->localColumn))
relations.append(r->localColumn);
foreach (RelationModel *r, newTable->foregionKeys())
if (!relations.contains(r->localColumn))
relations.append(r->localColumn);
QStringList columnSql;
foreach (QString fieldName, relations) {
RelationModel *newRelation = newTable->foregionKeyByField(fieldName);
RelationModel *oldRelation = 0;
if (oldTable)
oldRelation = oldTable->foregionKeyByField(fieldName);
QString buffer = diff(oldRelation, newRelation);
if (!buffer.isNull())
columnSql << buffer;
}
if (columnSql.count())
return "ALTER TABLE " + newTable->name() + "\n"
+ columnSql.join(",\n");
else
return "";
}
QString SqlGeneratorBase::diff(RelationModel *oldRel, RelationModel *newRel)
{
/*
CONSTRAINT FK_PersonOrder FOREIGN KEY (PersonID)
REFERENCES Persons(PersonID)
ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) REFERENCES %2(%3)
return QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) "
"REFERENCES %2(%3)")
.arg(newRelation->localColumn)
.arg(newRelation->masterTable->name())
.arg(newRelation->foreignColumn);
*/
if (!oldRel)
return QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) "
"REFERENCES %2(%3)")
.arg(newRel->localColumn)
.arg(newRel->masterTable->name())
.arg(newRel->foreignColumn);
if (!newRel)
return QString("ADD CONSTRAINT FK_%1 FOREIGN KEY (%1) "
"REFERENCES %2(%3)")
.arg(oldRel->localColumn)
.arg(oldRel->masterTable->name())
.arg(oldRel->foreignColumn);
// if (*oldRel == *newRel)
return "";
}
QString SqlGeneratorBase::join(const QString &mainTable, QString SqlGeneratorBase::join(const QString &mainTable,
const QList<RelationModel*> list, const QList<RelationModel*> list,
QStringList *order) QStringList *order)
@ -235,7 +336,7 @@ QString SqlGeneratorBase::join(const QStringList &list, QStringList *order)
//TODO: make this ungly code better and bugless :-) //TODO: make this ungly code better and bugless :-)
/* /*
* Known issues: * Known issues:
* Support onle near joins, far supports with medium table finding not support yet * Support only near joins, far supports with medium table finding not support yet
*/ */
if (!list.count()) if (!list.count())

View File

@ -65,10 +65,13 @@ public:
virtual QString fieldType(FieldModel *field) = 0; virtual QString fieldType(FieldModel *field) = 0;
virtual QString fieldDeclare(FieldModel *field); virtual QString fieldDeclare(FieldModel *field);
virtual QString relationDeclare(const RelationModel *relation);
virtual QStringList diff(DatabaseModel lastModel, DatabaseModel newModel); virtual QStringList diff(DatabaseModel lastModel, DatabaseModel newModel);
virtual QString diff(FieldModel *oldField, FieldModel *newField); virtual QString diff(FieldModel *oldField, FieldModel *newField);
virtual QString diff(TableModel *oldTable, TableModel *newTable); virtual QString diff(TableModel *oldTable, TableModel *newTable);
virtual QString diffRelation(TableModel *oldTable, TableModel *newTable);
virtual QString 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,
@ -120,7 +123,7 @@ public:
virtual QString phrase(const PhraseData *d) const; virtual QString phrase(const PhraseData *d) const;
virtual QString operatorString(const PhraseData::Condition &cond) const; virtual QString operatorString(const PhraseData::Condition &cond) const;
virtual void appendSkipTake(QString &sql, int skip = -1, int take = -1); virtual void appendSkipTake(QString &sql, int skip = -1, int take = -1);
private: protected:
QString createConditionalPhrase(const PhraseData *d) const; QString createConditionalPhrase(const PhraseData *d) const;
QString createFieldPhrase(const PhraseList &ph); QString createFieldPhrase(const PhraseList &ph);
QString createOrderPhrase(const PhraseList &ph); QString createOrderPhrase(const PhraseList &ph);

View File

@ -91,6 +91,7 @@ QString SqlServerGenerator::fieldType(FieldModel *field)
break; break;
default: default:
Q_UNREACHABLE();
dbType = ""; dbType = "";
} }
@ -133,23 +134,11 @@ QString SqlServerGenerator::escapeValue(const QVariant &v) const
return SqlGeneratorBase::escapeValue(v); return SqlGeneratorBase::escapeValue(v);
} }
//QString SqlServerGenerator::selectCommand(SqlGeneratorBase::AgregateType t, void SqlServerGenerator::appendSkipTake(QString &sql, int skip, int take)
// QString agregateArg, {
// QString tableName, if (take != -1 && skip != -1)
// QList<WherePhrase> &wheres, sql.append(QString(" OFFSET %1 ROWS FETCH NEXT %2 ROWS ONLY")
// QList<WherePhrase> &orders, .arg(skip).arg(take));
// QList<RelationModel*> joins, int skip, int take) }
//{
// QString command = SqlGeneratorBase::selectCommand(t, agregateArg,
// tableName,
// wheres, orders,
// joins, skip, take);
// if (take != -1 && skip != -1)
// command.append(QString("OFFSET %1 ROWS FETCH NEXT %2 ROWS ONLY")
// .arg(skip)
// .arg(take));
// return command;
//}
NUT_END_NAMESPACE NUT_END_NAMESPACE

View File

@ -37,12 +37,8 @@ public:
QString diff(FieldModel *oldField, FieldModel *newField); QString diff(FieldModel *oldField, FieldModel *newField);
QString escapeValue(const QVariant &v) const; QString escapeValue(const QVariant &v) const;
void appendSkipTake(QString &sql, int skip, int take);
// QString selectCommand(AgregateType t, QString agregateArg,
// QString tableName,
// QList<WherePhrase> &wheres,
// QList<WherePhrase> &orders,
// QList<RelationModel *> joins, int skip, int take);
}; };
NUT_END_NAMESPACE NUT_END_NAMESPACE

View File

@ -90,7 +90,7 @@ QList<FieldModel *> TableModel::fields() const
QList<RelationModel *> TableModel::foregionKeys() const QList<RelationModel *> TableModel::foregionKeys() const
{ {
return _foregionKeys; return _foreignKeys;
} }
QStringList TableModel::fieldsNames() const QStringList TableModel::fieldsNames() const
@ -237,9 +237,9 @@ TableModel::TableModel(int typeId, QString tableName)
fk->slaveTable = this; fk->slaveTable = this;
fk->localColumn = name + "Id"; fk->localColumn = name + "Id";
fk->localProperty = name; fk->localProperty = name;
fk->foregionColumn = value; fk->foreignColumn = value;
fk->masterClassName = value; fk->masterClassName = value;
_foregionKeys.append(fk); _foreignKeys.append(fk);
} }
if(type == __nut_FIELD){ if(type == __nut_FIELD){
@ -292,6 +292,7 @@ TableModel::TableModel(QJsonObject json, QString tableName)
_name = tableName; _name = tableName;
QJsonObject fields = json.value(__FIELDS).toObject(); QJsonObject fields = json.value(__FIELDS).toObject();
QJsonObject relations = json.value(__FOREIGN_KEYS).toObject();
foreach (QString key, fields.keys()) { foreach (QString key, fields.keys()) {
QJsonObject fieldObject = fields.value(key).toObject(); QJsonObject fieldObject = fields.value(key).toObject();
FieldModel *f = new FieldModel; FieldModel *f = new FieldModel;
@ -309,11 +310,16 @@ TableModel::TableModel(QJsonObject json, QString tableName)
_fields.append(f); _fields.append(f);
} }
if(json.keys().contains(__nut_AUTO_INCREMENT)) foreach (QString key, relations.keys()) {
field(json.value(__nut_AUTO_INCREMENT).toString())->isAutoIncrement = true; QJsonObject relObject = fields.value(key).toObject();
_foreignKeys.append(new RelationModel(relObject));
}
if(json.keys().contains(__nut_PRIMARY_KEY)) // if(json.keys().contains(__nut_AUTO_INCREMENT))
field(json.value(__nut_PRIMARY_KEY).toString())->isAutoIncrement = true; // field(json.value(__nut_AUTO_INCREMENT).toString())->isAutoIncrement = true;
// if(json.keys().contains(__nut_PRIMARY_KEY))
// field(json.value(__nut_PRIMARY_KEY).toString())->isAutoIncrement = true;
_allModels.insert(this); _allModels.insert(this);
} }
@ -322,6 +328,7 @@ QJsonObject TableModel::toJson() const
{ {
QJsonObject obj; QJsonObject obj;
QJsonObject fieldsObj; QJsonObject fieldsObj;
QJsonObject foreignKeysObj;
foreach (FieldModel *f, _fields) { foreach (FieldModel *f, _fields) {
QJsonObject fieldObj; QJsonObject fieldObj;
@ -337,28 +344,41 @@ QJsonObject TableModel::toJson() const
if(!f->defaultValue.isNull()) if(!f->defaultValue.isNull())
fieldObj.insert(__nut_DEFAULT_VALUE, f->defaultValue); fieldObj.insert(__nut_DEFAULT_VALUE, f->defaultValue);
fieldsObj.insert(f->name, fieldObj);
if(f->isAutoIncrement) if(f->isAutoIncrement)
obj.insert(__nut_PRIMARY_KEY, f->name); obj.insert(__nut_AUTO_INCREMENT, f->name);
if(f->isPrimaryKey) if(f->isPrimaryKey)
obj.insert(__nut_AUTO_INCREMENT, f->name); obj.insert(__nut_PRIMARY_KEY, f->name);
fieldsObj.insert(f->name, fieldObj);
} }
foreach (RelationModel *rel, _foreignKeys)
foreignKeysObj.insert(rel->localColumn, rel->toJson());
obj.insert(__FIELDS, fieldsObj); obj.insert(__FIELDS, fieldsObj);
obj.insert(__FOREIGN_KEYS, foreignKeysObj);
return obj; return obj;
} }
RelationModel *TableModel::foregionKey(QString otherTable) const RelationModel *TableModel::foregionKey(const QString &otherTable) const
{ {
foreach (RelationModel *fk, _foregionKeys) foreach (RelationModel *fk, _foreignKeys)
if(fk->masterClassName == otherTable) if(fk->masterClassName == otherTable)
return fk; return fk;
return 0; return 0;
} }
RelationModel *TableModel::foregionKeyByField(const QString &fieldName) const
{
foreach (RelationModel *fk, _foreignKeys)
if(fk->localColumn == fieldName)
return fk;
return 0;
}
QString TableModel::toString() const QString TableModel::toString() const
{ {
QStringList sl; QStringList sl;
@ -379,4 +399,59 @@ QString TableModel::primaryKey() const
return QString::null; return QString::null;
} }
FieldModel::FieldModel(const QJsonObject &json)
{
name = json.value(__NAME).toString();
type = static_cast<QVariant::Type>(json.value(__TYPE).toInt());
length = json.value(__nut_LEN).toInt();
notNull = json.value(__nut_NOT_NULL).toBool();
isAutoIncrement = json.value(__nut_AUTO_INCREMENT).toBool();
isPrimaryKey = json.value(__nut_PRIMARY_KEY).toBool();
defaultValue = json.value(__nut_DEFAULT_VALUE).toString();
}
QJsonObject FieldModel::toJson() const
{
QJsonObject fieldObj;
fieldObj.insert(__NAME, name);
fieldObj.insert(__TYPE, QString(QVariant::typeToName(type)));
fieldObj.insert(__nut_LEN, length);
fieldObj.insert(__nut_NOT_NULL, notNull);
fieldObj.insert(__nut_AUTO_INCREMENT, isAutoIncrement);
fieldObj.insert(__nut_PRIMARY_KEY, isPrimaryKey);
fieldObj.insert(__nut_DEFAULT_VALUE, defaultValue);
return fieldObj;
}
RelationModel::RelationModel(const QJsonObject &obj)
{
localColumn = obj.value("localColumn").toString();
localProperty = obj.value("localProperty").toString();
masterClassName = obj.value("masterClassName").toString();
foreignColumn = obj.value("foreignColumn").toString();
}
QJsonObject RelationModel::toJson() const
{
QJsonObject o;
o.insert("localColumn", localColumn);
o.insert("localProperty", localProperty);
o.insert("masterClassName", masterClassName);
o.insert("foreignColumn", foreignColumn);
return o;
}
bool operator ==(const RelationModel &l, const RelationModel &r)
{
return r.foreignColumn == l.foreignColumn
&& r.localColumn == l.localColumn
&& r.localProperty == l.localColumn
&& r.masterClassName == l.masterClassName;
}
bool operator !=(const RelationModel &l, const RelationModel &r)
{
return !(l == r);
}
NUT_END_NAMESPACE NUT_END_NAMESPACE

View File

@ -37,6 +37,8 @@ struct FieldModel{
} }
FieldModel(const QJsonObject &json);
QString name; QString name;
//TODO: QMetaType::Type?? //TODO: QMetaType::Type??
QVariant::Type type; QVariant::Type type;
@ -63,19 +65,30 @@ struct FieldModel{
bool operator !=(const FieldModel &f) const{ bool operator !=(const FieldModel &f) const{
return !(*this == f); return !(*this == f);
} }
QJsonObject toJson() const;
}; };
struct RelationModel{ struct RelationModel{
RelationModel() : localColumn(""), localProperty(""), slaveTable(0),
foreignColumn(""), masterTable(0), masterClassName("")
{}
RelationModel(const QJsonObject &obj);
//slave //slave
QString localColumn; QString localColumn;
QString localProperty; QString localProperty;
TableModel *slaveTable; TableModel *slaveTable;
//master //master
QString foregionColumn; QString foreignColumn;
TableModel *masterTable; TableModel *masterTable;
QString masterClassName; QString masterClassName;
QJsonObject toJson() const;
}; };
bool operator ==(const RelationModel &l, const RelationModel &r);
bool operator !=(const RelationModel &l, const RelationModel &r);
class TableModel class TableModel
{ {
public: public:
@ -91,7 +104,8 @@ public:
FieldModel *field(int n) const; FieldModel *field(int n) const;
FieldModel *field(QString name) const; FieldModel *field(QString name) const;
RelationModel *foregionKey(QString otherTable) const; RelationModel *foregionKey(const QString &otherTable) const;
RelationModel *foregionKeyByField(const QString &fieldName) const;
QString toString() const; QString toString() const;
@ -122,7 +136,7 @@ private:
QString _className; QString _className;
int _typeId; int _typeId;
QList<FieldModel*> _fields; QList<FieldModel*> _fields;
QList<RelationModel*> _foregionKeys; QList<RelationModel*> _foreignKeys;
static QSet<TableModel*>_allModels; static QSet<TableModel*>_allModels;
bool checkClassInfo(const QMetaClassInfo &classInfo, bool checkClassInfo(const QMetaClassInfo &classInfo,
QString &type, QString &name, QString &value); QString &type, QString &name, QString &value);