Qproperty (#118)

* initial commit

* new property schema ok
This commit is contained in:
Hamed Masafi 2021-01-27 18:20:31 +03:30 committed by GitHub
parent e70f68dfab
commit 424ecf7683
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 666 additions and 17 deletions

3
.gitmodules vendored
View File

@ -1,3 +1,6 @@
[submodule "src/nut/3rdparty/serializer"]
path = src/nut/3rdparty/serializer
url = https://github.com/HamedMasafi/Serializer.git
[submodule "3rdparty/serializer"]
path = 3rdparty/serializer
url = https://github.com/HamedMasafi/Serializer.git

1
3rdparty/serializer vendored Submodule

@ -0,0 +1 @@
Subproject commit 0e794d6317595d077e95e8a06f1f3a8c92543b05

View File

@ -28,6 +28,17 @@
private:
//Table
#define NUT_FIELD(type, name) \
private: \
NUT_INFO(__nut_FIELD, name, 0) \
public: \
static NUT_WRAP_NAMESPACE(FieldPhrase<type>)& name ## Field(){ \
static NUT_WRAP_NAMESPACE(FieldPhrase<type>) f = \
NUT_WRAP_NAMESPACE(FieldPhrase<type>) \
(staticMetaObject.className(), #name); \
return f; \
}
#define NUT_DECLARE_FIELD(type, name, read, write) \
Q_PROPERTY(type name READ read WRITE write) \
NUT_INFO(__nut_FIELD, name, 0) \
@ -113,7 +124,7 @@ public slots: \
return m_##n; \
}
#define NUT_FIELD(name) NUT_INFO(__nut_FIELD, name, 0)
//#define NUT_FIELD(name) NUT_INFO(__nut_FIELD, name, 0)
#define NUT_PRIMARY_KEY(x) NUT_INFO(__nut_PRIMARY_KEY, x, 0) \
public: \
QVariant primaryValue() const override { \

View File

@ -8,6 +8,7 @@ HEADERS += \
$$PWD/changelogtable.h \
$$PWD/database.h \
$$PWD/database_p.h \
$$PWD/propertysignalmapper.h \
$$PWD/query.h \
$$PWD/table.h \
$$PWD/table_p.h \
@ -18,6 +19,7 @@ SOURCES += \
$$PWD/bulkinserter.cpp \
$$PWD/changelogtable.cpp \
$$PWD/database.cpp \
$$PWD/propertysignalmapper.cpp \
$$PWD/query.cpp \
$$PWD/table.cpp \
$$PWD/tableset.cpp

View File

@ -0,0 +1,73 @@
/**************************************************************************
**
** 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 "propertysignalmapper.h"
#include "table.h"
NUT_BEGIN_NAMESPACE
QMap<QString, PropertySignalMapper::ClassData*> PropertySignalMapper::_data;
void PropertySignalMapper::map(Table *obj)
{
if (obj->_is_signals_mapped)
return;
ClassData *d;
const QMetaObject *mo = obj->metaObject();
auto className = QString::fromUtf8(mo->className());
if (_data.contains(className)) {
d = _data.value(className);
for (auto &p: d->properties)
QObject::connect(obj, p.notifySignal(), obj, d->propertyChanged);
} else {
d = new ClassData;
d->propertyChanged = mo->method(mo->indexOfSlot("propertyChanged()"));
for (int i = 0; i < mo->propertyCount(); ++i) {
QMetaProperty p = mo->property(i);
if (!strcmp(p.name(), "objectName"))
continue;
if (p.hasNotifySignal()) {
d->signalMaps.insert(QLatin1String(p.notifySignal().name()),
QLatin1String(p.name()));
d->properties.append(p);
QObject::connect(obj, p.notifySignal(), obj, d->propertyChanged);
}
}
_data.insert(className, d);
}
obj->_is_signals_mapped = true;
}
QString PropertySignalMapper::changedProperty(QObject *obj, int senderSignalIndex)
{
return _data.value(QString::fromUtf8(obj->metaObject()->className()))
->signalMaps.value(
QString::fromUtf8(obj->metaObject()->method(senderSignalIndex).name())
);
}
NUT_END_NAMESPACE

View File

@ -0,0 +1,49 @@
/**************************************************************************
**
** 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/>.
**
**************************************************************************/
#ifndef PROPERTYSIGNALMAPPER_H
#define PROPERTYSIGNALMAPPER_H
#include <QtNut/nut_global.h>
#include <QMetaMethod>
#include <QMetaProperty>
NUT_BEGIN_NAMESPACE
class Table;
class PropertySignalMapper
{
struct ClassData
{
QMetaMethod propertyChanged;
QList<QMetaProperty> properties;
QMap<QString, QString> signalMaps;
};
static QMap<QString, ClassData*> _data;
public:
static void map(Table *obj);
static QString changedProperty(QObject *obj, int senderSignalIndex);
};
NUT_END_NAMESPACE
#endif // PROPERTYSIGNALMAPPER_H

View File

@ -28,6 +28,7 @@
#include "databasemodel.h"
#include "abstractsqlgenerator.h"
#include "abstracttableset.h"
#include "propertysignalmapper.h"
NUT_BEGIN_NAMESPACE
@ -149,6 +150,17 @@ bool Table::setParentTable(Table *master, TableModel *masterModel, TableModel *m
return false;
}
void Table::propertyChanged()
{
auto pname = PropertySignalMapper::changedProperty(this, senderSignalIndex());
propertyChanged(pname);
}
void Table::init()
{
PropertySignalMapper::map(this);
}
AbstractTableSet *Table::parentTableSet() const
{
//Q_D(const Table);

View File

@ -72,11 +72,15 @@ public:
signals:
public slots:
void propertyChanged();
protected:
void init();
void propertyChanged(const QString &propName);
private:
bool _is_signals_mapped{false};
void setModel(TableModel *model);
// TableModel *myModel;
// Status _status;
@ -94,6 +98,7 @@ private:
template<class T>
friend class TableSet;
friend class AbstractTableSet;
friend class PropertySignalMapper;
};
NUT_END_NAMESPACE

View File

@ -54,6 +54,10 @@ public:
explicit TableSet(Database *parent);
explicit TableSet(Table *parent);
#ifndef NUT_RAW_POINTER
void append(T *t);
void append(QList<T*> t);
#endif
void append(Row<T> t);
void append(RowList<T> t);
void remove(Row<T> t);
@ -113,6 +117,21 @@ Q_OUTOFLINE_TEMPLATE Row<T> TableSet<T>::operator[](int i) const
return at(i);
}
#ifndef NUT_RAW_POINTER
template<class T>
Q_OUTOFLINE_TEMPLATE void TableSet<T>::append(T *t)
{
append(QSharedPointer<T>(t));
}
template<class T>
Q_OUTOFLINE_TEMPLATE void TableSet<T>::append(QList<T*> t)
{
for (auto &table: t)
append(table);
}
#endif
template<class T>
Q_OUTOFLINE_TEMPLATE void TableSet<T>::append(Row<T> t)
{

View File

@ -5,6 +5,7 @@ SUBDIRS += \
tst_benckmark \
tst_datatypes \
tst_phrases \
tst_properties \
tst_quuid \
tst_generators \
tst_upgrades \

View File

@ -4,7 +4,64 @@
Comment::Comment(QObject *parent) : Table(parent)
{
init();
}
int Comment::id() const
{
return m_id;
}
QString Comment::message() const
{
return m_message;
}
QDateTime Comment::saveDate() const
{
return m_saveDate;
}
qreal Comment::point() const
{
return m_point;
}
void Comment::setId(int id)
{
if (m_id == id)
return;
m_id = id;
emit idChanged(m_id);
}
void Comment::setMessage(QString message)
{
if (m_message == message)
return;
m_message = message;
emit messageChanged(m_message);
}
void Comment::setSaveDate(QDateTime saveDate)
{
if (m_saveDate == saveDate)
return;
m_saveDate = saveDate;
emit saveDateChanged(m_saveDate);
}
void Comment::setPoint(qreal point)
{
qWarning("Floating point comparison needs context sanity check");
if (qFuzzyCompare(m_point, point))
return;
m_point = point;
emit pointChanged(m_point);
}
NUT_FOREIGN_KEY_IMPLEMENT(Comment, Post, int, post, post, setPost)

View File

@ -15,17 +15,44 @@ class Comment : public Table
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
Q_PROPERTY(QDateTime saveDate READ saveDate WRITE setSaveDate NOTIFY saveDateChanged)
Q_PROPERTY(qreal point READ point WRITE setPoint NOTIFY pointChanged)
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
NUT_DECLARE_FIELD(QString, message, message, setMessage)
NUT_DECLARE_FIELD(QDateTime, saveDate, saveDate, setSaveDate)
NUT_DECLARE_FIELD(qreal, point, point, setPoint)
NUT_FIELD(int, id)
NUT_FIELD(QString, message)
NUT_FIELD(QDateTime, saveDate)
NUT_FIELD(qreal, point)
NUT_FOREIGN_KEY_DECLARE(Post, int, post, post, setPost)
NUT_FOREIGN_KEY_DECLARE(User, int, author, author, setAuthor)
int m_id;
QString m_message;
QDateTime m_saveDate;
qreal m_point;
public:
Q_INVOKABLE explicit Comment(QObject *parentTableSet = nullptr);
int id() const;
QString message() const;
QDateTime saveDate() const;
qreal point() const;
public slots:
void setId(int id);
void setMessage(QString message);
void setSaveDate(QDateTime saveDate);
void setPoint(qreal point);
signals:
void idChanged(int id);
void messageChanged(QString message);
void saveDateChanged(QDateTime saveDate);
void pointChanged(qreal point);
};
Q_DECLARE_METATYPE(Comment*)

View File

@ -8,7 +8,77 @@ Post::Post(QObject *parent) : Table(parent),
m_comments(new TableSet<Comment>(this)),
m_scores(new TableSet<Score>(this))
{
init();
}
int Post::id() const
{
return m_id;
}
QString Post::title() const
{
return m_title;
}
QDateTime Post::saveDate() const
{
return m_saveDate;
}
QString Post::body() const
{
return m_body;
}
bool Post::isPublic() const
{
return m_isPublic;
}
void Post::setId(int id)
{
if (m_id == id)
return;
m_id = id;
emit idChanged(m_id);
}
void Post::setTitle(QString title)
{
if (m_title == title)
return;
m_title = title;
emit titleChanged(m_title);
}
void Post::setSaveDate(QDateTime saveDate)
{
if (m_saveDate == saveDate)
return;
m_saveDate = saveDate;
emit saveDateChanged(m_saveDate);
}
void Post::setBody(QString body)
{
if (m_body == body)
return;
m_body = body;
emit bodyChanged(m_body);
}
void Post::setPublic(bool isPublic)
{
if (m_isPublic == isPublic)
return;
m_isPublic = isPublic;
emit isPublicChanged(m_isPublic);
}
NUT_IMPLEMENT_CHILD_TABLE(Post, Comment, comments)

View File

@ -17,27 +17,55 @@ class Post : public Table
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QDateTime saveDate READ saveDate WRITE setSaveDate NOTIFY saveDateChanged)
Q_PROPERTY(QString body READ body WRITE setBody NOTIFY bodyChanged)
Q_PROPERTY(bool isPublic READ isPublic WRITE setPublic NOTIFY isPublicChanged)
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
NUT_FIELD(int, id)
NUT_NOT_NULL(title)
NUT_LEN(title, 50)
NUT_DECLARE_FIELD(QString, title, title, setTitle)
NUT_FIELD(QString, title)
NUT_DECLARE_FIELD(QDateTime, saveDate, saveDate, setSaveDate)
NUT_FIELD(QDateTime, saveDate)
NUT_DECLARE_FIELD(QString, body, body, setBody)
NUT_DECLARE_FIELD(bool, isPublic, isPublic, setPublic)
NUT_FIELD(QString, body)
NUT_FIELD(bool, isPublic)
NUT_DECLARE_CHILD_TABLE(Comment, comments)
NUT_DECLARE_CHILD_TABLE(Score, scores)
int m_id;
QString m_title;
QDateTime m_saveDate;
QString m_body;
bool m_isPublic;
public:
Q_INVOKABLE Post(QObject *parentTableSet = nullptr);
int id() const;
QString title() const;
QDateTime saveDate() const;
QString body() const;
bool isPublic() const;
signals:
void idChanged(int id);
void titleChanged(QString title);
void saveDateChanged(QDateTime saveDate);
void bodyChanged(QString body);
void isPublicChanged(bool isPublic);
public slots:
void setId(int id);
void setTitle(QString title);
void setSaveDate(QDateTime saveDate);
void setBody(QString body);
void setPublic(bool isPublic);
};
Q_DECLARE_METATYPE(Post*)

View File

@ -7,7 +7,49 @@ User::User(QObject *tableSet) : Table(tableSet),
m_comments(new TableSet<Comment>(this)),
m_scores(new TableSet<Score>(this))
{
init();
}
int User::id() const
{
return m_id;
}
QString User::username() const
{
return m_username;
}
QString User::password() const
{
return m_password;
}
void User::setId(int id)
{
if (m_id == id)
return;
m_id = id;
emit idChanged(m_id);
}
void User::setUsername(QString username)
{
if (m_username == username)
return;
m_username = username;
emit usernameChanged(m_username);
}
void User::setPassword(QString password)
{
if (m_password == password)
return;
m_password = password;
emit passwordChanged(m_password);
}
NUT_IMPLEMENT_CHILD_TABLE(User, Comment, comments)

View File

@ -17,22 +17,44 @@ class User : public Nut::Table
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged)
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
NUT_FIELD(int, id)
NUT_NOT_NULL(username)
NUT_LEN(username, 50)
NUT_DECLARE_FIELD(QString, username, username, setUsername)
NUT_FIELD(QString, username)
NUT_NOT_NULL(password)
NUT_LEN(password, 50)
NUT_DECLARE_FIELD(QString, password, password, setPassword)
NUT_FIELD(QString, password)
NUT_DECLARE_CHILD_TABLE(Comment, comments)
NUT_DECLARE_CHILD_TABLE(Score, scores)
int m_id;
QString m_username;
QString m_password;
public:
Q_INVOKABLE User(QObject *parentTableSet = nullptr);
int id() const;
QString username() const;
QString password() const;
public slots:
void setId(int id);
void setUsername(QString username);
void setPassword(QString password);
signals:
void idChanged(int id);
void usernameChanged(QString username);
void passwordChanged(QString password);
};
Q_DECLARE_METATYPE(User*)

View File

@ -139,16 +139,16 @@ void BasicTest::updatePostOnTheFly()
void BasicTest::selectPublicts()
{
auto q = db.posts()->query()
auto publinPostsCount = db.posts()->query()
.where(Post::isPublicField())
.count();
auto q2 = db.posts()->query()
auto nonPublicPostsCount = db.posts()->query()
.where(!Post::isPublicField())
.count();
QCOMPARE(q, 1);
QCOMPARE(q2, 1);
QCOMPARE(publinPostsCount, 1);
QCOMPARE(nonPublicPostsCount, 1);
}
void BasicTest::selectPosts()

View File

@ -0,0 +1,8 @@
#include "sampledatabase.h"
#include "sampletable.h"
SampleDataBase::SampleDataBase() : Nut::Database()
, m_items(new Nut::TableSet<SampleTable>(this))
{
}

View File

@ -0,0 +1,17 @@
#ifndef SAMPLEDATABASE_H
#define SAMPLEDATABASE_H
#include <QtNut/Database>
class SampleTable;
class SampleDataBase : public Nut::Database
{
Q_OBJECT
NUT_DB_VERSION(1)
NUT_DECLARE_TABLE(SampleTable, items)
public:
SampleDataBase();
};
#endif // SAMPLEDATABASE_H

View File

@ -0,0 +1,48 @@
#include "sampletable.h"
SampleTable::SampleTable(QObject *parent) : Nut::Table(parent)
{
init();
}
int SampleTable::id() const
{
return m_id;
}
QString SampleTable::name() const
{
return m_name;
}
QString SampleTable::lastName() const
{
return m_lastName;
}
void SampleTable::setId(int id)
{
if (m_id == id)
return;
m_id = id;
emit idChanged(m_id);
}
void SampleTable::setName(QString name)
{
if (m_name == name)
return;
m_name = name;
emit nameChanged(m_name);
}
void SampleTable::setLastName(QString lastName)
{
if (m_lastName == lastName)
return;
m_lastName = lastName;
emit lastNameChanged(m_lastName);
}

View File

@ -0,0 +1,40 @@
#ifndef SAMPLETABLE_H
#define SAMPLETABLE_H
#include <QtNut/table.h>
class SampleTable : public Nut::Table
{
Q_OBJECT
Q_PROPERTY(int id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString lastName READ lastName WRITE setLastName NOTIFY lastNameChanged)
int m_id;
QString m_name;
QString m_lastName;
NUT_PRIMARY_KEY(id)
NUT_FIELD(int, id)
NUT_FIELD(QString, name)
NUT_FIELD(QString, lastName)
public:
explicit SampleTable(QObject *parent = nullptr);
int id() const;
QString name() const;
QString lastName() const;
public slots:
void setId(int id);
void setName(QString name);
void setLastName(QString lastName);
signals:
void idChanged(int id);
void nameChanged(QString name);
void lastNameChanged(QString lastName);
};
#endif // SAMPLETABLE_H

View File

@ -0,0 +1,69 @@
#include <QtTest>
#include "tst_properties.h"
#include "sampledatabase.h"
#include "sampletable.h"
#include "../common/consts.h"
#include <QtNut/Query>
PropertiesTest::PropertiesTest(QObject *parent) : QObject(parent)
{
}
void PropertiesTest::initTestCase()
{
REGISTER(SampleTable);
REGISTER(SampleDataBase);
db.setDriver(DRIVER);
db.setHostName(HOST);
db.setDatabaseName(DATABASE);
db.setUserName(USERNAME);
db.setPassword(PASSWORD);
bool ok = db.open();
QVERIFY(ok);
db.items()->query().remove();
}
void PropertiesTest::insert()
{
auto s = new SampleTable;
s->setId(1);
s->setName("hamed");
s->setLastName("masafi");
db.items()->append(s);
auto c = db.saveChanges();
QCOMPARE(c, 1);
}
void PropertiesTest::select()
{
auto item = db.items()->query()
.first();
QCOMPARE(item->name(), "hamed");
}
void PropertiesTest::parallelUpdate()
{
auto item1 = db.items()->query()
.first();
{
auto item2 = db.items()->query()
.first();
item2->setLastName("masafi 2");
db.saveChanges();
}
item1->setName("hamed 2");
db.saveChanges();
auto item = db.items()->query()
.first();
QCOMPARE(item->name(), "hamed 2");
QCOMPARE(item->lastName(), "masafi 2");
}
QTEST_MAIN(PropertiesTest)

View File

@ -0,0 +1,26 @@
#ifndef TST_PROPERTIES_H
#define TST_PROPERTIES_H
#include <QtCore/QObject>
#include <QtCore/qglobal.h>
#include "sampledatabase.h"
class PropertiesTest : public QObject
{
Q_OBJECT
SampleDataBase db;
public:
explicit PropertiesTest(QObject *parent = nullptr);
signals:
private slots:
void initTestCase();
void insert();
void select();
void parallelUpdate();
};
#endif // TST_PROPERTIES_H

View File

@ -0,0 +1,19 @@
QT += testlib sql
TARGET = tst_basic
TEMPLATE = app
CONFIG += warn_on c++11
include(../common/nut-lib.pri)
SOURCES += \
sampledatabase.cpp \
sampletable.cpp \
tst_properties.cpp
HEADERS += \
sampledatabase.h \
sampletable.h \
tst_properties.h