Merge pull request #47 from HamedMasafi/dev

merge dev into master
This commit is contained in:
Hamed Masafi 2019-07-10 12:25:27 +04:30 committed by GitHub
commit 9d494a1887
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1271 additions and 289 deletions

View File

@ -1,6 +1,10 @@
#Based on https://github.com/pcolby/libqtaws/blob/master/.travis.yml
language: cpp
branches:
only:
- master
os:
- linux
- osx

Binary file not shown.

2
3rdparty/serializer vendored

@ -1 +1 @@
Subproject commit f9d81fcc6244271b3926891b0c86554e1e6b967e
Subproject commit e0bdec4a7c93b4d4307fd89200000ff817a86c99

104
README.md
View File

@ -2,14 +2,8 @@
# Nut
## Build result
| Branch | Status |
| ------------- |:-------------:|
| master | [![Build Status](https://travis-ci.org/HamedMasafi/Nut.svg?branch=master)](https://travis-ci.org/HamedMasafi/Nut) |
| dev | [![Build Status](https://travis-ci.org/HamedMasafi/Nut.svg?branch=dev)](https://travis-ci.org/HamedMasafi/Nut) |
[![Build Status](https://travis-ci.org/HamedMasafi/Nut.svg?branch=master)](https://travis-ci.org/HamedMasafi/Nut)
[![GitLicense](https://gitlicense.com/badge/hamedmasafi/nut)](https://gitlicense.com/license/hamedmasafi/nut)
[![Codacy
Badge](https://api.codacy.com/project/badge/Grade/f3802610beb946068f6cd2c2b6608a8b)](https://www.codacy.com/app/HamedMasafi/Nut?utm_source=github.com&utm_medium=referral&utm_content=HamedMasafi/Nut&utm_campaign=Badge_Grade)
@ -23,95 +17,15 @@ Badge](https://api.codacy.com/project/badge/Grade/f3802610beb946068f6cd2c2b6608a
- Automatically create and update database
- IDE auto complete support, No hard-code nedded
- Table join detect
- Supported types:
- Suppor every Qt types. [Full list](doc/datatypes.md)
| Type | Sqlite | MySql | Postgresql| Ms Sql server |
|--------|--------|--------|--------|--------|
| QBitArray | BLOB | VARBINARY | BYTEA | VARBINARY (MAX) |
| QByteArray | BLOB | BLOB | BYTEA | VARBINARY (MAX) |
| QChar | NCHAR(1) | CHAR(1) | CHAR(1) | CHAR(1) |
| QColor | TEXT | TEXT | TEXT | TEXT |
| QDate | DATE | DATE | DATE | DATE |
| QDateTime | DATETIME | DATETIME | TIMESTAMP | DATETIME |
| QJsonArray | TEXT | TEXT | JSON | TEXT |
| QJsonDocument | TEXT | TEXT | JSON | TEXT |
| QJsonObject | TEXT | TEXT | JSON | TEXT |
| QJsonValue | TEXT | TEXT | JSON | TEXT |
| QLine | TEXT | TEXT | LINE | TEXT |
| QLineF | TEXT | TEXT | LINE | TEXT |
| QPoint | TEXT | POINT | POINT | GEOMETRY |
| QPointF | TEXT | POINT | POINT | GEOMETRY |
| QPolygon | TEXT | POLYGON | POLYGON | TEXT |
| QPolygonF | TEXT | POLYGON | POLYGON | TEXT |
| QRect | TEXT | TEXT | BOX | TEXT |
| QRectF | TEXT | TEXT | BOX | TEXT |
| QSize | TEXT | TEXT | TEXT | TEXT |
| QSizeF | TEXT | TEXT | TEXT | TEXT |
| QString | TEXT | TEXT | TEXT | NVARCHAR(MAX) |
| QStringList | TEXT[^*] | TEXT | TEXT[] | TEXT |
| QTime | TIME | TIME | TIME | TIME |
| QUrl | TEXT | TEXT | TEXT | TEXT |
| QUuid | TEXT | VARCHAR(64) | UUID | UNIQUEIDENTIFIER |
| bool | BOOLEAN | BOOLEAN | BOOLEAN | BIT |
| char | TINYINT | CHAR(1) | CHAR(1) | CHAR(1) |
| double | DOUBLE | REAL | REAL | REAL |
| float | FLOAT | FLOAT | FLOAT | FLOAT(24) |
| int | INT | INT | INTEGER | INT |
| long | MEDIUMINT | BIGINT | BIGINT | BIGINT |
| qlonglong | BIGINT | BIGINT | BIGINT | BIGINT |
| qulonglong | BIGINT UNSIGNED | BIGINT | BIGINT | BIGINT |
| short | SMALLINT | SMALLINT | SMALLINT | SMALLINT |
| signed char | TINYINT | TINYINT | SMALLINT | TINYINT |
| uchar | TINYINT UNSIGNED | TINYINT | SMALLINT | TINYINT |
| uint | INT UNSIGNED | INT | INTEGER | INT |
| ulong | MEDIUMINT UNSIGNED | BIGINT | BIGINT | BIGINT |
| ushort | SMALLINT UNSIGNED | SMALLINT | SMALLINT | SMALLINT |
[^*]: Using internal store/restore serialization
## Sample Codes
### Read data from database:
```cpp
auto q = db.posts()->createQuery();
q->setWhere(Post::idField() == postId);
auto posts = q->toList();
// now posts is a QList<Post*> contain all posts in
// database that has id equal to postId variable
auto post = q->first();
// post is first row in database that its id is equal to postId
```
### Adding to database:
```cpp
Post *newPost = new Post;
newPost->setTitle("post title");
db.posts()->append(newPost);
for(int i = 0 ; i < 3; i++){
Comment *comment = new Comment;
comment->setMessage("comment #" + QString::number(i));
newPost->comments()->append(comment);
}
db.saveChanges();
```
### Modify database data:
```cpp
auto q = db.posts()->createQuery();
q->setWhere(Post::idField() == postId);
Post *post = q->first();
if(post) {
post->setTitle("new name");
db.saveChanges();
} else {
qWarning("No post found!");
}
```
## Getting start
* [Sample codes](doc/start.md)
* [Shared pointer and regular mode](sharedpointer.md)
* [Create database class](database.md)
* [Create table class](table.md)
* [Using queries](query.md)
* [Supported data types](datatypes.md)
### Donate
Butcoin address: 1Dn1WHKkaxanXe4cTGDk4cFRRABxLUpEVj

View File

@ -1 +1,3 @@
#QT -= gui
DEFINES += NUT_PATH=\\\"$$PWD/../../\\\"

39
doc/database.md Normal file
View File

@ -0,0 +1,39 @@
Database class must inherits from Nut::Database class.
Database class can have NUT_DB_VERSION for declaring version number, version will be stored in database if upgrade needed.
```cpp
NUT_DB_VERSION(major, minor)
```
for every table in database NUT_DECLARE_TABLE macro should be use, usage:
```cpp
NUT_DECLARE_TABLE(class_name, table_name)
```
Sample database class:
```cpp
#include <Database>
class Post;
class Comment;
class WeblogDatabase : public Nut::Database
{
Q_OBJECT
NUT_DB_VERSION(1)
NUT_DECLARE_TABLE(Post, post)
NUT_DECLARE_TABLE(Comment, comment)
public:
WeblogDatabase();
};
```
Child tables should initalize in constructor, Example:
```cpp
WeblogDatabase::WeblogDatabase() : Nut::Database()
, m_posts(new TableSet<Post>(this))
, m_comments(new TableSet<Comment>(this))
{
}
```

41
doc/datatypes.md Normal file
View File

@ -0,0 +1,41 @@
| Type | Sqlite | MySql | Postgresql| Ms Sql server |
|--------|--------|--------|--------|--------|
| bool | BOOLEAN | BOOLEAN | BOOLEAN | BIT |
| QBitArray | BLOB | VARBINARY | BYTEA | VARBINARY (MAX) |
| QByteArray | BLOB | BLOB | BYTEA | VARBINARY (MAX) |
| QDate | DATE | DATE | DATE | DATE |
| QDateTime | DATETIME | DATETIME | TIMESTAMP | DATETIME |
| QTime | TIME | TIME | TIME | TIME |
| double | DOUBLE | REAL | REAL | REAL |
| float | FLOAT | FLOAT | FLOAT | FLOAT(24) |
| signed char | TINYINT | TINYINT | SMALLINT | tinyint |
| char | TINYINT | CHAR(1) | CHAR(1) | CHAR(1) |
| uchar | TINYINT UNSIGNED | TINYINT | SMALLINT | tinyint |
| short | SMALLINT | SMALLINT | SMALLINT | smallint |
| ushort | SMALLINT UNSIGNED | SMALLINT | SMALLINT | smallint |
| int | INT | INT | INTEGER | INT |
| uint | INT UNSIGNED | INT | INTEGER | INT |
| long | MEDIUMINT | BIGINT | BIGINT | bigint |
| ulong | MEDIUMINT UNSIGNED | BIGINT | BIGINT | bigint |
| qlonglong | BIGINT | BIGINT | BIGINT | bigint |
| qulonglong | BIGINT UNSIGNED | BIGINT | BIGINT | bigint |
| QChar | NCHAR(1) | CHAR(1) | CHAR(1) | CHAR(1) |
| QUrl | TEXT | TEXT | TEXT | TEXT |
| QJsonArray | TEXT | TEXT | JSONB | TEXT |
| QJsonValue | TEXT | TEXT | JSONB | TEXT |
| QJsonObject | TEXT | TEXT | JSONB | TEXT |
| QJsonDocument | TEXT | TEXT | JSONB | TEXT |
| QPoint | TEXT | TEXT | POINT | GEOMETRY |
| QPointF | TEXT | TEXT | POINT | GEOMETRY |
| QSize | TEXT | TEXT | TEXT | TEXT |
| QSizeF | TEXT | TEXT | TEXT | TEXT |
| QLine | TEXT | TEXT | LINE | TEXT |
| QLineF | TEXT | TEXT | LINE | TEXT |
| QRect | TEXT | TEXT | BOX | TEXT |
| QRectF | TEXT | TEXT | BOX | TEXT |
| QPolygon | TEXT | TEXT | POLYGON | TEXT |
| QPolygonF | TEXT | TEXT | POLYGON | TEXT |
| QStringList | TEXT | TEXT | TEXT[] | TEXT |
| QColor | TEXT | TEXT | TEXT | TEXT |
| QUuid | TEXT | TEXT | UUID | UNIQUEIDENTIFIER |
| QString | TEXT | TEXT | TEXT | NVARCHAR(MAX) |

67
doc/query.md Normal file
View File

@ -0,0 +1,67 @@
# Creating query
```cpp
auto q = db.posts().query();
```
You can also create query in one command:
```cpp
auto result = db.posts().query()
->where(Post::idField() == 1)
->toList();
```
Now, _result_ contains **QList\<QSharedPointer\<Post\>\>** and can be used in code. query has other commands like: sum, avg, max, min and etc
## Getting first record in query
```cpp
auto post = db.posts().query()
->setWhete(Post::idField() == 1)
->first();
if(post)
qDebug() << "Post found in database";
else
qDebug() << "No post found!";
```
## Sorting result
```cpp
auto posts = db.posts().query()
->whete(Post::idField() == 1)
->orderBy(Post::idField())
->toList();
```
Also you can sort descending by adding **!** to field name
```cpp
auto posts = db.posts().query()
->whete(Post::idField() == 1)
->orderBy(!Post::idField())
->toList();
```
## Selecting single field
```cpp
auto ids = db.posts().query()
->select(Post::idField());
//ids is type of QList<int>
```
## Getting sum, count, min, max
```cpp
auto q = db.posts().query();
auto sum = q.sum(Post::idField());
auto max = q.max(Post::idField());
auto min = q.min(Post::idField());
auto count = q.count(Post::idField());
```
## Checking field exists in list of values
```cpp
auto post = db.posts().query()
->where(Post::idField().in(QList<int>() << 1 << 2 << 3 << 4) || Post::isAccepted())
->first();
```
Or
```cpp
auto post = db.posts().query()
->where(Post::idField().in({1, 2, 3, 4}) || Post::isAccepted())
->first();
```

59
doc/sharedpointer.md Normal file
View File

@ -0,0 +1,59 @@
Nut can compile in *shared pointer* mode or *regular* mode
In *shared pointer* mode reqults of queries is QList<QSharedPointer<T>> and in *regular* mode results are QList<T*>
Almost in every case shared pointer mode is better, But nut support regular mode for backward comptability.
To compiling in *shared pointer* define **NUT_SHARED_POINTER** macro
Nut has template alias
```cpp
#ifdef NUT_SHARED_POINTER
template <class T>
using RowList = QList<QSharedPointer<T>>;
template <typename T>
using Row = QSharedPointer<T>;
#else
template <typename T>
using RowList = QList<T*>;
template <typename T>
using Row = T*;
#endif
```
In other words these types are defined by this table:
| Mode | Nut::Row | Nut::RowList |
|------ |----- |--------- |
|Regular|T* | QList\<T\*\> |
|Shared pointer|QSharedPointer\<T\> | QList\<QSharedPointer\<T\>\> |
For the integration of your source, you can use these aliases.
Ans also Nut::create<T>() method are defined for two mode
```cpp
#ifdef NUT_SHARED_POINTER
template<class T>
inline Row<T> create(QObject *parent) {
return QSharedPointer<T>(new T(parent));
}
#else
template<class T>
inline Row<T> create() {
return new T;
}
#endif
```
So you can use the Nut::create function without considering in what way the library is being compiled. Example:
```cpp
auto post = Nut::create<Post>();
```
In above example if *NUT_SHARED_POINTER* is defined *post* is *QSharedPointer<Post>* else is *Post\**
I recommand use *NUT_SHARED_POINTER* always!

45
doc/start.md Normal file
View File

@ -0,0 +1,45 @@
### Read data from database:
```cpp
auto posts = db.posts()->query()
->where(Post::idField() == postId)
->toList();
// now posts is a QList<Post*> contain all posts in
// database that has id equal to postId variable
auto post = q->first();
// post is first row in database that its id is equal to postId
```
### Adding to database:
```cpp
auto newPost = Nut::create<Post>();
newPost->setTitle("post title");
db.posts()->append(newPost);
for(int i = 0 ; i < 3; i++){
// Below line same as new Comment in non shared pointer mode
// or QSharedPointer<Comment>(new Comment) in shared_pointer mode
auto comment = Nut::create<Comment>();
comment->setMessage("comment #" + QString::number(i));
newPost->comments()->append(comment);
}
db.saveChanges();
```
### Modify database data:
```cpp
auto post = db.posts()->query()
->where(Post::idField() == postId)
->first();
if(post) {
post->setTitle("new name");
db.saveChanges();
} else {
qWarning("No post found!");
}
```

70
doc/table.md Normal file
View File

@ -0,0 +1,70 @@
The class must inherits from Table class
## Add primary key field
Primary key can be auto increment
```cpp
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
```
for declaring primary key use _NUT_PRIMARY_KEY_ macro, if primary key is auto increment use _NUT_PRIMARY_AUTO_INCREMENT_
| Macro | Description |
| ----------------------------- |:------------------------------------------------|
| NUT_PRIMARY_KEY(x) | The field *x* is primary key |
| NUT_AUTO_INCREMENT(x) | The field *x* is auto increment |
| NUT_PRIMARY_AUTO_INCREMENT(x) | The field *x* is primary key and auto increment |
## Declare field
```cpp
NUT_DECLARE_FIELD(type, property_name, read_method_name, write_method_name)
```
## Additional meta data
| Macro | Description |
| ----------------------------- |:-------------------------------------------------|
| NUT_NOT_NULL(x) | The field *x* is not allowed to store NULL value |
| NUT_LEN(x, len) | Max length of *x* is *len* in string types and in numeric typed field *x* will be store in *len* bytes |
| NUT_DEFAULT_VALUE(x, def) | Default value of *x* is *def* |
| NUT_UNIQUE(x) | Field *x* is unique (Not imlemented yet!) |
| NUT_DISPLAY_NAME(field, name) | Sets display name for field (used in model creation |
## Sample table
```cpp
class Post : public Table
{
Q_OBJECT
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
NUT_NOT_NULL(title)
NUT_LEN(title, 50)
NUT_DECLARE_FIELD(QString, title, title, setTitle)
NUT_DECLARE_FIELD(QDateTime, saveDate, saveDate, setSaveDate)
NUT_LEN(body, 200)
NUT_DECLARE_FIELD(QString, body, body, setBody)
public:
explicit Post(QObject *tableSet = 0);
};
```
## Declare child table
If current table has one-to-many relation ship it must be declared. For example post table has a slave table named comment, every post has many comment:
```cpp
NUT_DECLARE_CHILD_TABLE(Comment, comments)
```
First argument id table name and second is field name, m_comments must be initalized in constructor:
```cpp
Post::Post(QObject *parent) : Table(parent),
m_comments(new TableSet<Comment>(this)), m_id(0), m_title("")
{
}
```
For more example take a look at _tests/common_ folder

View File

@ -87,12 +87,53 @@ public: \
NUT_DECLARE_FIELD(keytype, name##Id, read##Id, write##Id) \
NUT_INFO(__nut_FOREIGN_KEY, name, type) \
Nut::Row<type> m_##name; \
public: \
public slots: \
Nut::Row<type> read() const { return m_##name ; } \
void write(Nut::Row<type> name){ \
Q_INVOKABLE void write(Nut::Row<type> name){ \
m_##name = name; \
}
#define NUT_FOREIGN_KEY_DECLARE(type, keytype, name, read, write) \
NUT_INFO(__nut_FIELD, name##Id, 0) \
NUT_INFO(__nut_FOREIGN_KEY, name, type) \
Nut::Row<type> m_##name; \
keytype m_##name##Id; \
Q_PROPERTY(Nut::Row<type> name READ read WRITE write) \
Q_PROPERTY(keytype name##Id READ read##Id WRITE write##Id) \
public: \
Nut::Row<type> read() const; \
void write(Nut::Row<type> name); \
static NUT_WRAP_NAMESPACE(FieldPhrase<keytype>)& name##Id ## Field(){ \
static NUT_WRAP_NAMESPACE(FieldPhrase<keytype>) f = \
NUT_WRAP_NAMESPACE(FieldPhrase<keytype>) \
(staticMetaObject.className(), #name); \
return f; \
} \
keytype read##Id() const; \
void write##Id(keytype name##Id);
#define NUT_FOREIGN_KEY_IMPLEMENT(class, type, keytype, name, read, write) \
\
Nut::Row<type> class::read() const { return m_##name ; } \
void class::write(Nut::Row<type> name){ \
propertyChanged(QT_STRINGIFY2(name##Id)); \
m_##name = name; \
m_##name##Id = name->primaryValue().value<keytype>(); \
} \
\
keytype class::read##Id() const{ \
if (m_##name) \
return m_##name->primaryValue().value<keytype>(); \
return m_##name##Id; \
} \
void class::write##Id(keytype name##Id){ \
propertyChanged(QT_STRINGIFY2(name##Id)); \
m_##name##Id = name##Id; \
m_##name = nullptr; \
propertyChanged(QT_STRINGIFY2(name##Id)); \
}
#define NUT_DECLARE_CHILD_TABLE(type, n) \
private: \
NUT_WRAP_NAMESPACE(TableSet)<type> *m_##n; \
@ -110,9 +151,20 @@ public: \
}
#define NUT_FIELD(name) NUT_INFO(__nut_FIELD, name, 0)
#define NUT_PRIMARY_KEY(x) NUT_INFO(__nut_PRIMARY_KEY, x, 0)
#define NUT_PRIMARY_KEY(x) NUT_INFO(__nut_PRIMARY_KEY, x, 0) \
public: \
QVariant primaryValue() const override { \
return property(#x); \
} \
void setPrimaryValue(const QVariant &value) override { \
setProperty(#x, value); \
} \
private:
#define NUT_AUTO_INCREMENT(x) NUT_INFO(__nut_AUTO_INCREMENT, x, 0)
#define NUT_PRIMARY_AUTO_INCREMENT(x) NUT_INFO(__nut_PRIMARY_KEY_AI, x, 0)
#define NUT_PRIMARY_AUTO_INCREMENT(x) NUT_INFO(__nut_PRIMARY_KEY_AI, x, 0)\
NUT_PRIMARY_KEY(X) NUT_AUTO_INCREMENT(X)
#define NUT_DISPLAY_NAME(field, name) NUT_INFO(__nut_DISPLAY, field, name)
#define NUT_UNIQUE(x) NUT_INFO(__nut_UNIQUE, x, 0)
#define NUT_LEN(field, len) NUT_INFO(__nut_LEN, field, len)
@ -250,6 +302,32 @@ inline T *get(const QSharedPointer<T> row) {
#endif
//template<class C, typename T>
//struct ForeignKeyData {
// Nut::Row<C> _table;
// T _id;
// ForeignKeyData() : _table(nullptr)
// {}
// void setTable(Nut::Row<C> t) {
// _table = t;
// _id = t->primaryValue().value<T>();
// }
// Nut::Row<C> table() const {
// return _table;
// }
// void setValue(const T& val) {
// _table = nullptr;
// _id = val;
// }
// T value() const {
// if (_table)
// return _table->primaryValue().value<T>();
// return _id;
// }
//};
NUT_END_NAMESPACE
#endif // SYNTAX_DEFINES_H

View File

@ -290,4 +290,41 @@ bool MySqlGenerator::readInsideParentese(QString &text, QString &out)
// return command;
//}
QString MySqlGenerator::createConditionalPhrase(const PhraseData *d) const
{
if (!d)
return QString();
PhraseData::Condition op = d->operatorCond;
//apply not (!)
if (d->isNot) {
if (op < 20)
op = static_cast<PhraseData::Condition>((op + 10) % 20);
}
if (d->type == PhraseData::WithVariant) {
if (op == PhraseData::AddYears)
return QString("DATE_ADD(%2, INTERVAL %1 YEAR)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddMonths)
return QString("DATE_ADD(%2, INTERVAL %1 MONTH)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddDays)
return QString("DATE_ADD(%2, INTERVAL %1 DAY)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddHours)
return QString("DATE_ADD(%2, INTERVAL %1 HOUR)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddMinutes)
return QString("DATE_ADD(%2, INTERVAL %1 MINUTE)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddSeconds)
return QString("DATE_ADD(%2, INTERVAL %1 SECOND)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
}
return SqlGeneratorBase::createConditionalPhrase(d);
}
NUT_END_NAMESPACE

View File

@ -31,11 +31,12 @@ class MySqlGenerator : public SqlGeneratorBase
public:
explicit MySqlGenerator(Database *parent = 0);
QString fieldType(FieldModel *field);
QString escapeValue(const QVariant &v) const;
QVariant unescapeValue(const QMetaType::Type &type, const QVariant &dbValue);
QString fieldType(FieldModel *field) override;
QString escapeValue(const QVariant &v) const override;
QVariant unescapeValue(const QMetaType::Type &type, const QVariant &dbValue) override;
// QString phrase(const PhraseData *d) const;
// QString selectCommand(AgregateType t, QString agregateArg, QString tableName, QList<WherePhrase> &wheres, QList<WherePhrase> &orders, QList<RelationModel *> joins, int skip, int take);
QString createConditionalPhrase(const PhraseData *d) const override;
private:
bool readInsideParentese(QString &text, QString &out);
};

View File

@ -328,12 +328,38 @@ QString PostgreSqlGenerator::createConditionalPhrase(const PhraseData *d) const
if (!d)
return QString();
PhraseData::Condition op = d->operatorCond;
//apply not (!)
if (d->isNot) {
if (op < 20)
op = static_cast<PhraseData::Condition>((op + 10) % 20);
}
if (d->type == PhraseData::WithVariant) {
if (isPostGisType(d->operand.type()) && d->operatorCond == PhraseData::Equal) {
return QString("%1 ~= %2")
.arg(SqlGeneratorBase::createConditionalPhrase(d->left),
escapeValue(d->operand));
}
if (op == PhraseData::AddYears)
return QString("DATEADD(year, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddMonths)
return QString("DATEADD(month, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddDays)
return QString("DATEADD(day, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddHours)
return QString("DATEADD(hour, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddMinutes)
return QString("DATEADD(minute, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddSeconds)
return QString("DATEADD(second, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
}
return SqlGeneratorBase::createConditionalPhrase(d);

View File

@ -446,16 +446,17 @@ QString SqlGeneratorBase::insertRecord(Table *t, QString tableName)
QStringList values;
foreach (QString f, t->changedProperties())
if (f != key)
values.append(escapeValue(t->property(f.toLatin1().data())));
QString changedPropertiesText = QString();
QSet<QString> props = t->changedProperties();
foreach (QString s, props) {
QString changedPropertiesText = QString();
foreach (QString f, props) {
if (f == key)
continue;
values.append(escapeValue(t->property(f.toLatin1().data())));
if (changedPropertiesText != "")
changedPropertiesText.append(", ");
changedPropertiesText.append(s);
changedPropertiesText.append(f);
}
sql = QString("INSERT INTO %1 (%2) VALUES (%3)")
.arg(tableName, changedPropertiesText, values.join(", "));
@ -784,6 +785,31 @@ void SqlGeneratorBase::removeTableNames(QString &command)
command = command.replace("[" + m->className() + "].", "");
}
QString SqlGeneratorBase::dateTimePartName(const PhraseData::Condition &op) const
{
switch (op) {
case PhraseData::AddYears:
case PhraseData::AddYearsDateTime:
return "YEAR";
case PhraseData::AddMonths:
case PhraseData::AddMonthsDateTime:
return "MONTH";
case PhraseData::AddDays:
case PhraseData::AddDaysDateTime:
return "DAY";
case PhraseData::AddHours:
case PhraseData::AddHoursDateTime:
return "HOUR";
case PhraseData::AddMinutes:
case PhraseData::AddMinutesDateTime:
return "MINUTE";
case PhraseData::AddSeconds:
case PhraseData::AddSecondsDateTime:
return "SECOND";
}
return QString();
}
//QString SqlGeneratorBase::deleteCommand(QList<WherePhrase> &wheres,
// QString tableName)
//{
@ -972,7 +998,7 @@ QString SqlGeneratorBase::createConditionalPhrase(const PhraseData *d) const
break;
case PhraseData::WithVariant:
if (op == PhraseData::AddYears)
/* if (op == PhraseData::AddYears)
ret = QString("DATEADD(year, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::AddMonths)
@ -990,7 +1016,12 @@ QString SqlGeneratorBase::createConditionalPhrase(const PhraseData *d) const
else if (op == PhraseData::AddSeconds)
ret = QString("DATEADD(second, %1, %2)")
.arg(d->operand.toString(), createConditionalPhrase(d->left));
else if (op == PhraseData::DatePartYear)
else */if (op == PhraseData::Between) {
QVariantList list = d->operand.toList();
ret = QString("%1 BETWEEN %2 AND %3")
.arg(createConditionalPhrase(d->left), escapeValue(list.at(0)), escapeValue(list.at(1)));
} else if (op == PhraseData::DatePartYear)
ret = QString("DATEPART(year, %1)")
.arg(d->operand.toString());
else if (op == PhraseData::DatePartMonth)

View File

@ -162,6 +162,7 @@ protected:
void replaceTableNames(QString &command);
void removeTableNames(QString &command);
QString dateTimePartName(const PhraseData::Condition &op) const;
};
NUT_END_NAMESPACE

View File

@ -205,4 +205,116 @@ QString SqliteGenerator::primaryKeyConstraint(const TableModel *table) const
// return sql;
}
QString SqliteGenerator::createConditionalPhrase(const PhraseData *d) const
{
if (!d)
return QString();
PhraseData::Condition op = d->operatorCond;
//apply not (!)
if (d->isNot) {
if (op < 20)
op = static_cast<PhraseData::Condition>((op + 10) % 20);
}
if (d->type == PhraseData::WithVariant) {
switch (op) {
case PhraseData::AddYears:
case PhraseData::AddMonths:
case PhraseData::AddDays: {
int i = d->operand.toInt();
return QString("DATE(%1,'%2 %3')")
.arg(createConditionalPhrase(d->left),
(i < 0 ? "" : "+") + QString::number(i),
dateTimePartName(op));
break;
}
case PhraseData::AddHours:
case PhraseData::AddMinutes:
case PhraseData::AddSeconds: {
int i = d->operand.toInt();
return QString("TIME(%1,'%2 %3')")
.arg(createConditionalPhrase(d->left),
(i < 0 ? "" : "+") + QString::number(i),
dateTimePartName(op));
break;
}
case PhraseData::AddYearsDateTime:
case PhraseData::AddMonthsDateTime:
case PhraseData::AddDaysDateTime:
case PhraseData::AddHoursDateTime:
case PhraseData::AddMinutesDateTime:
case PhraseData::AddSecondsDateTime: {
int i = d->operand.toInt();
return QString("DATETIME(%1,'%2 %3')")
.arg(createConditionalPhrase(d->left),
(i < 0 ? "" : "+") + QString::number(i),
dateTimePartName(op));
break;
}
}
}
if (d->type == PhraseData::WithoutOperand) {
switch (op) {
case PhraseData::DatePartYear:
return QString("CAST(strftime('%Y', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
case PhraseData::DatePartMonth:
return QString("CAST(strftime('%m', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
case PhraseData::DatePartDay:
return QString("CAST(strftime('%d', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
case PhraseData::DatePartHour:
return QString("CAST(strftime('%H', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
case PhraseData::DatePartMinute:
return QString("CAST(strftime('%M', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
case PhraseData::DatePartSecond:
return QString("CAST(strftime('%S', %1) AS INT)")
.arg(createConditionalPhrase(d->left));
// case PhraseData::DatePartMilisecond:
// return QString("CAST(strftime('%Y', %1) AS INT)")
// .arg(createConditionalPhrase(d->left));
}
}
return SqlGeneratorBase::createConditionalPhrase(d);
}
QString SqliteGenerator::escapeValue(const QVariant &v) const
{
if (v.type() == QVariant::Time)
return "'" + v.toTime().toString("HH:mm:ss") + "'";
if (v.type() == QVariant::Date)
return "'" + v.toDate().toString("yyyy-MM-dd") + "'";
if (v.type() == QVariant::DateTime)
return "'" + v.toDateTime().toString("yyyy-MM-dd HH:mm:ss") + "'";
return SqlGeneratorBase::escapeValue(v);
}
QVariant SqliteGenerator::unescapeValue(const QMetaType::Type &type, const QVariant &dbValue)
{
if (type == QMetaType::QDateTime)
return dbValue.toDateTime();
if (type == QMetaType::QTime)
return dbValue.toTime();
if (type == QMetaType::QDate)
return dbValue.toDate();
return SqlGeneratorBase::unescapeValue(type, dbValue);
}
NUT_END_NAMESPACE

View File

@ -41,6 +41,10 @@ public:
QString primaryKeyConstraint(const TableModel *table) const override;
QStringList diff(TableModel *oldTable, TableModel *newTable) override;
QString createConditionalPhrase(const PhraseData *d) const override;
QString escapeValue(const QVariant &v) const override;
QVariant unescapeValue(const QMetaType::Type &type, const QVariant &dbValue) override;
};
NUT_END_NAMESPACE

View File

@ -20,3 +20,128 @@
#include "datephrase.h"
NUT_BEGIN_NAMESPACE
FieldPhrase<QDate>::FieldPhrase(const char *className, const char *s) :
AbstractFieldPhrase(className, s)
{}
ConditionalPhrase FieldPhrase<QDate>::addYears(int years) {
return ConditionalPhrase(this, PhraseData::AddYears, years);
}
ConditionalPhrase FieldPhrase<QDate>::addMonths(int months) {
return ConditionalPhrase(this, PhraseData::AddMonths, months);
}
ConditionalPhrase FieldPhrase<QDate>::addDays(int days) {
return ConditionalPhrase(this, PhraseData::AddDays, days);
}
ConditionalPhrase FieldPhrase<QDate>::year() {
return ConditionalPhrase(this, PhraseData::DatePartYear);
}
ConditionalPhrase FieldPhrase<QDate>::month() {
return ConditionalPhrase(this, PhraseData::DatePartMonth);
}
ConditionalPhrase FieldPhrase<QDate>::day() {
return ConditionalPhrase(this, PhraseData::DatePartDay);
}
FieldPhrase<QTime>::FieldPhrase(const char *className, const char *s) :
AbstractFieldPhrase(className, s)
{}
ConditionalPhrase FieldPhrase<QTime>::addHours(int hours) {
return ConditionalPhrase(this, PhraseData::AddHours, hours);
}
ConditionalPhrase FieldPhrase<QTime>::addMinutes(int minutes) {
return ConditionalPhrase(this, PhraseData::AddMinutes, minutes);
}
ConditionalPhrase FieldPhrase<QTime>::addSeconds(int seconds) {
return ConditionalPhrase(this, PhraseData::AddSeconds, seconds);
}
ConditionalPhrase FieldPhrase<QTime>::hour() {
return ConditionalPhrase(this, PhraseData::DatePartHour);
}
ConditionalPhrase FieldPhrase<QTime>::minute() {
return ConditionalPhrase(this, PhraseData::DatePartMinute);
}
ConditionalPhrase FieldPhrase<QTime>::second() {
return ConditionalPhrase(this, PhraseData::DatePartSecond);
}
ConditionalPhrase FieldPhrase<QTime>::msec() {
return ConditionalPhrase(this, PhraseData::DatePartMilisecond);
}
FieldPhrase<QDateTime>::FieldPhrase(const char *className, const char *s) :
AbstractFieldPhrase(className, s)
{}
ConditionalPhrase FieldPhrase<QDateTime>::addYears(int years) {
return ConditionalPhrase(this, PhraseData::AddYearsDateTime, years);
}
ConditionalPhrase FieldPhrase<QDateTime>::addMonths(int months) {
return ConditionalPhrase(this, PhraseData::AddMonthsDateTime, months);
}
ConditionalPhrase FieldPhrase<QDateTime>::addDays(int days) {
return ConditionalPhrase(this, PhraseData::AddDaysDateTime, days);
}
ConditionalPhrase FieldPhrase<QDateTime>::addHours(int hours) {
return ConditionalPhrase(this, PhraseData::AddHoursDateTime, hours);
}
ConditionalPhrase FieldPhrase<QDateTime>::addMinutes(int minutes) {
return ConditionalPhrase(this, PhraseData::AddMinutesDateTime, minutes);
}
ConditionalPhrase FieldPhrase<QDateTime>::addSeconds(int seconds) {
return ConditionalPhrase(this, PhraseData::AddSecondsDateTime, seconds);
}
ConditionalPhrase FieldPhrase<QDateTime>::year() {
return ConditionalPhrase(this, PhraseData::DatePartYear);
}
ConditionalPhrase FieldPhrase<QDateTime>::month() {
return ConditionalPhrase(this, PhraseData::DatePartMonth);
}
ConditionalPhrase FieldPhrase<QDateTime>::day() {
return ConditionalPhrase(this, PhraseData::DatePartDay);
}
ConditionalPhrase FieldPhrase<QDateTime>::hour() {
return ConditionalPhrase(this, PhraseData::DatePartHour);
}
ConditionalPhrase FieldPhrase<QDateTime>::minute() {
return ConditionalPhrase(this, PhraseData::DatePartMinute);
}
ConditionalPhrase FieldPhrase<QDateTime>::second() {
return ConditionalPhrase(this, PhraseData::DatePartSecond);
}
ConditionalPhrase FieldPhrase<QDateTime>::msec() {
return ConditionalPhrase(this, PhraseData::DatePartMilisecond);
}
COMMON_OPERATORS_IMPL(QDate)
COMMON_OPERATORS_IMPL(QTime)
COMMON_OPERATORS_IMPL(QDateTime)
NUT_END_NAMESPACE

View File

@ -27,145 +27,96 @@
NUT_BEGIN_NAMESPACE
#define COMMON_OPERATORS_DECL(T) \
AssignmentPhrase operator =(const T &other); \
ConditionalPhrase operator <(const QVariant &other); \
ConditionalPhrase operator <=(const QVariant &other); \
ConditionalPhrase operator >(const QVariant &other); \
ConditionalPhrase operator >=(const QVariant &other); \
ConditionalPhrase between(const QVariant &min, const QVariant &max);
template<typename>
struct __is_date_helper
: public std::false_type { };
#define COMMON_OPERATORS_IMPL(T) \
AssignmentPhrase FieldPhrase<T>::operator =(const T &other) { \
return AssignmentPhrase(this, other); \
} \
ConditionalPhrase FieldPhrase<T>::operator <(const QVariant &other) { \
return ConditionalPhrase(this, PhraseData::Less, other); \
} \
ConditionalPhrase FieldPhrase<T>::operator <=(const QVariant &other) { \
return ConditionalPhrase(this, PhraseData::LessEqual, other); \
} \
ConditionalPhrase FieldPhrase<T>::operator >(const QVariant &other) { \
return ConditionalPhrase(this, PhraseData::Greater, other); \
} \
ConditionalPhrase FieldPhrase<T>::operator >=(const QVariant &other) { \
return ConditionalPhrase(this, PhraseData::GreaterEqual, other); \
} \
ConditionalPhrase FieldPhrase<T>::between(const QVariant &min, const QVariant &max) \
{ \
return ConditionalPhrase(this, PhraseData::Between, \
QVariantList() << min << max); \
}
template<>
struct __is_date_helper<QTime>
: public std::true_type { };
template<>
struct __is_date_helper<QDate>
: public std::true_type { };
template<>
struct __is_date_helper<QDateTime>
: public std::true_type { };
template<typename _Tp>
struct is_date
: public __is_date_helper<typename std::remove_cv<_Tp>::type>::type
{ };
template <class T, class P>
inline bool is_valid_template() {return false;}
template <>
inline bool is_valid_template<QDateTime, QTime>() {return true;}
template <>
inline bool is_valid_template<QDateTime, QDate>() {return true;}
template <>
inline bool is_valid_template<QDate, QDate>() {return true;}
template <>
inline bool is_valid_template<QTime, QTime>() {return true;}
template <typename T>
class NUT_EXPORT FieldPhrase<T, typename std::enable_if<is_date<T>::value>::type>
: public AbstractFieldPhrase
class NUT_EXPORT FieldPhrase<QDate> : public AbstractFieldPhrase
{
public:
FieldPhrase(const char *className, const char *s) :
AbstractFieldPhrase(className, s)
{}
FieldPhrase(const char *className, const char *s);
AssignmentPhrase operator =(const T &other) {
return AssignmentPhrase(this, other);
}
COMMON_OPERATORS_DECL(QDate)
ConditionalPhrase operator <(const QVariant &other) {
return ConditionalPhrase(this, PhraseData::Less, other);
}
ConditionalPhrase operator <=(const QVariant &other) {
return ConditionalPhrase(this, PhraseData::LessEqual, other);
}
ConditionalPhrase operator >(const QVariant &other) {
return ConditionalPhrase(this, PhraseData::Greater, other);
}
ConditionalPhrase operator >=(const QVariant &other) {
return ConditionalPhrase(this, PhraseData::GreaterEqual, other);
}
ConditionalPhrase addYears(int years);
ConditionalPhrase addMonths(int months);
ConditionalPhrase addDays(int days);
ConditionalPhrase between(const QVariant &min, const QVariant &max)
{
return ConditionalPhrase(this, PhraseData::Between,
QVariantList() << min << max);
}
ConditionalPhrase year();
ConditionalPhrase month();
ConditionalPhrase day();
};
// template<class P = T,
// class std::enable_if<std::is_same<P, QDateTime>::value, int>::type = 0>
ConditionalPhrase addYears(int val) {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddYears, val);
}
ConditionalPhrase addMonths(int val) {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddMonths, val);
}
ConditionalPhrase addDays(int val) {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddDays, val);
}
template<>
class NUT_EXPORT FieldPhrase<QTime> : public AbstractFieldPhrase
{
public:
FieldPhrase(const char *className, const char *s);
ConditionalPhrase addHours(int val) {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddHours, val);
}
ConditionalPhrase addMinutes(int val) {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddMinutes, val);
}
ConditionalPhrase addSeconds(int val) {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::AddSeconds, val);
}
COMMON_OPERATORS_DECL(QTime)
ConditionalPhrase year() {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartYear);
}
ConditionalPhrase month() {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartMonth);
}
ConditionalPhrase day() {
if (!is_valid_template<T, QDate>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartDay);
}
ConditionalPhrase hour() {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartHour);
}
ConditionalPhrase minute() {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartMinute);
}
ConditionalPhrase second() {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartSecond);
}
ConditionalPhrase msec() {
if (!is_valid_template<T, QTime>())
return ConditionalPhrase();
return ConditionalPhrase(this, PhraseData::DatePartMilisecond);
}
ConditionalPhrase addHours(int hours);
ConditionalPhrase addMinutes(int minutes);
ConditionalPhrase addSeconds(int seconds);
ConditionalPhrase hour();
ConditionalPhrase minute();
ConditionalPhrase second();
ConditionalPhrase msec();
};
template<>
class NUT_EXPORT FieldPhrase<QDateTime> : public AbstractFieldPhrase
{
public:
FieldPhrase(const char *className, const char *s);
COMMON_OPERATORS_DECL(QDateTime)
ConditionalPhrase addYears(int year);
ConditionalPhrase addMonths(int months);
ConditionalPhrase addDays(int days);
ConditionalPhrase addHours(int hours);
ConditionalPhrase addMinutes(int minutes);
ConditionalPhrase addSeconds(int seconds);
ConditionalPhrase year();
ConditionalPhrase month();
ConditionalPhrase day();
ConditionalPhrase hour();
ConditionalPhrase minute();
ConditionalPhrase second();
ConditionalPhrase msec();
};
NUT_END_NAMESPACE

View File

@ -64,6 +64,14 @@ public:
AddMinutes,
AddSeconds,
// sqlite need to know works with qdate, qtime or qdatetime
AddYearsDateTime,
AddMonthsDateTime,
AddDaysDateTime,
AddHoursDateTime,
AddMinutesDateTime,
AddSecondsDateTime,
DatePartYear,
DatePartMonth,
DatePartDay,

View File

@ -289,11 +289,13 @@ Q_OUTOFLINE_TEMPLATE RowList<T> Query<T>::toList(int count)
//create table row
Table *table;
Row<T> shp;
if (data.table->className() == d->className) {
table = new T();
#ifdef NUT_SHARED_POINTER
auto shp = QSharedPointer<T>(qobject_cast<T*>(table));
shp = QSharedPointer<T>(qobject_cast<T*>(table));
returnList.append(shp);
d->tableSet->add(shp);
#else
returnList.append(dynamic_cast<T*>(table));
#endif
@ -317,12 +319,27 @@ Q_OUTOFLINE_TEMPLATE RowList<T> Query<T>::toList(int count)
for (int i = 0; i < data.masters.count(); ++i) {
int master = data.masters[i];
table->setProperty(data.masterFields[i].toLocal8Bit().data(),
#ifdef NUT_SHARED_POINTER
QString mName = QString("set%1").arg(levels[master].lastRow->metaObject()->className());
QString type = QString("Nut::Row<%1>").arg(levels[master].lastRow->metaObject()->className());
bool ok = table->metaObject()->invokeMethod(table,
mName.toLocal8Bit().data(),
QGenericArgument(type.toLatin1().data(), levels[master].lastRow));
#else
bool ok = table->setProperty(data.masterFields[i].toLocal8Bit().data(),
QVariant::fromValue(levels[master].lastRow));
#endif
table->setParentTableSet(
levels[master].lastRow->childTableSet(
data.table->className()));
if (!ok)
qWarning("Unable to set property %s::%s",
table->metaObject()->className(), data.masterFields[i].toLocal8Bit().data());
auto tableset = levels[master].lastRow->childTableSet(
data.table->className());
table->setParentTableSet(tableset);
#ifdef NUT_SHARED_POINTER
tableset->add(qSharedPointerCast<Table>(shp));
#endif
}
table->setStatus(Table::FeatchedFromDB);
@ -330,7 +347,7 @@ Q_OUTOFLINE_TEMPLATE RowList<T> Query<T>::toList(int count)
table->clear();
//set last created row
data.lastRow = table;
data.lastRow = /*QSharedPointer<Table>*/(table);
} //while
} // while

View File

@ -73,27 +73,10 @@ QVariant SqlModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole) {
Row<Table> t = d->rows.at(index.row());
QVariant v = t->property(d->model->field(index.column())->name.toLocal8Bit().data());
// emit beforeShowText(index.column(), v);
if (_renderer != nullptr)
v = _renderer(index.column(), v);
return v;
// LogData *d = dataList.at(index.row());
// switch (index.column()) {
// case COL_ID:
// return index.row() + 1;
// case COL_Type: {
// return typeText(d->type);
// }
// case COL_TITLE:
// return d->title;
// case COL_File:
// return d->file;
// case COL_Function:
// return d->function;
// case COL_Line:
// return d->line;
// }
}
return QVariant();
}
@ -101,9 +84,11 @@ QVariant SqlModel::data(const QModelIndex &index, int role) const
void SqlModel::setRows(RowList<Table> rows)
{
d.detach();
beginRemoveRows(QModelIndex(), 0, d->rows.count());
d->rows.clear();
endRemoveRows();
if (d->rows.count()) {
beginRemoveRows(QModelIndex(), 0, d->rows.count());
d->rows.clear();
endRemoveRows();
}
beginInsertRows(QModelIndex(), 0, rows.count());
d->rows = rows;
endInsertRows();

View File

@ -55,14 +55,8 @@ public:
int save(Database *db);
// Q_DECL_DEPRECATED
// QString primaryKey() const;
// Q_DECL_DEPRECATED
// bool isPrimaryKeyAutoIncrement() const;
// Q_DECL_DEPRECATED
// QVariant primaryValue() const;
virtual QVariant primaryValue() const = 0;
virtual void setPrimaryValue(const QVariant &value) = 0;
Status status() const;
void setStatus(const Status &status);

View File

@ -42,6 +42,9 @@ TableSetBase::~TableSetBase()
{
foreach (Table *t, data->tables)
t->setParentTableSet(nullptr);
foreach (Row<Table> t, data->childs)
t->setParentTableSet(nullptr);
}
int TableSetBase::save(Database *db, bool cleanUp)
@ -61,9 +64,12 @@ int TableSetBase::save(Database *db, bool cleanUp)
|| t->status() == Table::Modified
|| t->status() == Table::Deleted){
rowsAffected += t->save(db);
if(cleanUp)
#ifndef NUT_SHARED_POINTER
t->deleteLater();
#else
remove(t);
#endif
}
}
@ -76,7 +82,7 @@ int TableSetBase::save(Database *db, bool cleanUp)
void TableSetBase::clearChilds()
{
#ifndef NUT_SHARED_POINTER
foreach (Table *t, data->_childRows)
foreach (Table *t, data->childRows)
t->deleteLater();
#endif
data->childRows.clear();
@ -98,6 +104,18 @@ void TableSetBase::remove(Table *t)
data->childRows.removeOne(get(t));
}
void TableSetBase::add(Row<Table> t)
{
data.detach();
data->childs.append(t);
}
void TableSetBase::remove(Row<Table> t)
{
data.detach();
data->childs.removeOne(t);
}
QString TableSetBase::childClassName() const
{
return data->childClassName;

View File

@ -57,10 +57,13 @@ protected:
// QString _childClassName;
QExplicitlySharedDataPointer<TableSetBaseData> data;
private:
public://TODO: change this to private
void add(Table* t);
void remove(Table *t);
void add(Row<Table> t);
void remove(Row<Table> t);
friend class Table;
friend class QueryBase;
};

View File

@ -1,7 +1,11 @@
#include "comment.h"
#include "post.h"
#include "user.h"
Comment::Comment(QObject *parent) : Table(parent),
m_author(Q_NULLPTR), m_post(Q_NULLPTR)
Comment::Comment(QObject *parent) : Table(parent)
{
}
NUT_FOREIGN_KEY_IMPLEMENT(Comment, Post, int, post, post, setPost)
NUT_FOREIGN_KEY_IMPLEMENT(Comment, User, int, author, author, setAuthor)

View File

@ -21,8 +21,8 @@ class Comment : public Table
NUT_DECLARE_FIELD(QDateTime, saveDate, saveDate, setSaveDate)
NUT_DECLARE_FIELD(qreal, point, point, setPoint)
NUT_FOREGION_KEY(Post, int, post, post, setPost)
NUT_FOREGION_KEY(User, int, author, author, setAuthor)
NUT_FOREIGN_KEY_DECLARE(Post, int, post, post, setPost)
NUT_FOREIGN_KEY_DECLARE(User, int, author, author, setAuthor)
public:
Q_INVOKABLE explicit Comment(QObject *parentTableSet = nullptr);

View File

@ -1,6 +1,11 @@
#include "score.h"
#include "user.h"
#include "post.h"
Score::Score(QObject *parent) : Nut::Table(parent)
{
}
NUT_FOREIGN_KEY_IMPLEMENT(Score, Post, int, post, post, setPost)
NUT_FOREIGN_KEY_IMPLEMENT(Score, User, QUuid, author, author, setAuthor)

View File

@ -1,6 +1,7 @@
#ifndef SCORE_H
#define SCORE_H
#include <QUuid>
#include "table.h"
class User;
@ -14,8 +15,8 @@ class Score : public Nut::Table
NUT_DECLARE_FIELD(int, score, score, setScore)
NUT_FOREGION_KEY(Post, int, post, post, setPost)
NUT_FOREGION_KEY(User, QUuid, author, author, setAuthor)
NUT_FOREIGN_KEY_DECLARE(Post, int, post, post, setPost)
NUT_FOREIGN_KEY_DECLARE(User, QUuid, author, author, setAuthor)
public:
Q_INVOKABLE Score(QObject *parent = Q_NULLPTR);

View File

@ -3,12 +3,11 @@ TEMPLATE = subdirs
SUBDIRS += \
tst_basic \
tst_benckmark \
# tst_commands \
tst_datatypes \
#tst_join \
tst_phrases \
tst_quuid \
tst_generators \
tst_upgrades \
tst_json
tst_json \
tst_datetime

View File

@ -78,7 +78,7 @@ void BasicTest::createPost()
comment->setMessage("comment #" + QString::number(i));
comment->setSaveDate(QDateTime::currentDateTime());
comment->setAuthorId(user->id());
db.comments()->append(comment);
newPost->comments()->append(comment);
}
for (int i = 0; i < 10; ++i) {
auto score = Nut::create<Score>();

9
test/tst_datetime/db.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "db.h"
#include "sampletable.h"
DB::DB() : Nut::Database (),
m_sampleTables(new Nut::TableSet<SampleTable>(this))
{
}

21
test/tst_datetime/db.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef DB_H
#define DB_H
#include "database.h"
class SampleTable;
class DB : public Nut::Database
{
Q_OBJECT
NUT_DB_VERSION(1)
NUT_DECLARE_TABLE(SampleTable, sampleTables)
public:
DB();
};
Q_DECLARE_METATYPE(DB*)
#endif // DB_H

View File

@ -0,0 +1,6 @@
#include "sampletable.h"
SampleTable::SampleTable(QObject *parent) : Nut::Table (parent)
{
}

View File

@ -0,0 +1,27 @@
#ifndef SAMPLETABLE_H
#define SAMPLETABLE_H
#include <QTime>
#include <QDateTime>
#include <QDate>
#include "table.h"
class SampleTable : public Nut::Table
{
Q_OBJECT
NUT_PRIMARY_AUTO_INCREMENT(id)
NUT_DECLARE_FIELD(int, id, id, setId)
NUT_DECLARE_FIELD(QDate, d, d, setD)
NUT_DECLARE_FIELD(QTime, t, t, setT)
NUT_DECLARE_FIELD(QDateTime, dt, dt, setDT)
public:
Q_INVOKABLE SampleTable(QObject *parent = Q_NULLPTR);
};
Q_DECLARE_METATYPE(SampleTable*)
#endif // SAMPLETABLE_H

View File

@ -0,0 +1,211 @@
#include <QtTest>
#include <QDebug>
#include <QSqlError>
#include <QElapsedTimer>
#include "consts.h"
#include "tst_datetime.h"
#include "query.h"
#include "tableset.h"
#include "tablemodel.h"
#include "databasemodel.h"
#include "sampletable.h"
DateTimeTest::DateTimeTest(QObject *parent) : QObject(parent)
{
_baseDateTime = QDateTime::currentDateTime();
}
void DateTimeTest::initTestCase()
{
//register all entities with Qt-MetaType mechanism
REGISTER(SampleTable);
REGISTER(DB);
db.setDriver(DRIVER);
db.setHostName(HOST);
db.setDatabaseName(DATABASE);
db.setUserName(USERNAME);
db.setPassword(PASSWORD);
QTEST_ASSERT(db.open());
db.sampleTables()->query()->remove();
}
#define TEST_DATE(date, command, n) \
do { \
auto s = Nut::create<SampleTable>(); \
s->setD(date); \
db.sampleTables()->append(s); \
db.saveChanges(); \
auto count = db.sampleTables()->query() \
->where(SampleTable::dField().command(n) == date.command(n)) \
->count(); \
QTEST_ASSERT(count); \
} while (false)
#define TEST_TIME(time, command, n, num) \
do { \
auto s = Nut::create<SampleTable>(); \
s->setT(time); \
db.sampleTables()->append(s); \
db.saveChanges(); \
auto count = db.sampleTables()->query() \
->where(SampleTable::tField().command(n) == time.addSecs(num)) \
->count(); \
QTEST_ASSERT(count); \
} while (false)
#define TEST_DATE2(datetime, command, n) \
do { \
auto s = Nut::create<SampleTable>(); \
s->setDT(datetime); \
db.sampleTables()->append(s); \
db.saveChanges(); \
auto count = db.sampleTables()->query() \
->where(SampleTable::dtField().command(n) == datetime.command(n)) \
->count(); \
QTEST_ASSERT(count); \
} while (false)
#define TEST_TIME2(datetime, command, n, num) \
do { \
auto s = Nut::create<SampleTable>(); \
s->setDT(datetime); \
db.sampleTables()->append(s); \
db.saveChanges(); \
auto count = db.sampleTables()->query() \
->where(SampleTable::dtField().command(n) == datetime.addSecs(num)) \
->count(); \
QTEST_ASSERT(count); \
} while (false)
#define MINUTE(m) m * 60
#define HOUR(h) MINUTE(h) * 60
void DateTimeTest::dateAdd()
{
QDate d = QDate::currentDate();
TEST_DATE(d, addYears, 10);
TEST_DATE(d, addMonths, 10);
TEST_DATE(d, addDays, 10);
TEST_DATE(d, addYears, -10);
TEST_DATE(d, addMonths, -10);
TEST_DATE(d, addDays, -10);
}
void DateTimeTest::timeAdd()
{
QTime t = QTime::currentTime();
TEST_TIME(t, addHours, 10, HOUR(10));
TEST_TIME(t, addMinutes, 10, MINUTE(10));
TEST_TIME(t, addSeconds, 10, 10);
TEST_TIME(t, addHours, -10, HOUR(-10));
TEST_TIME(t, addMinutes, -10, MINUTE(-10));
TEST_TIME(t, addSeconds, -10, -10);
}
void DateTimeTest::dateTimeAdd()
{
QDateTime dt = QDateTime::currentDateTime();
TEST_DATE2(dt, addYears, 10);
TEST_DATE2(dt, addMonths, 10);
TEST_DATE2(dt, addDays, 10);
TEST_DATE2(dt, addYears, -10);
TEST_DATE2(dt, addMonths, -10);
TEST_DATE2(dt, addDays, -10);
TEST_TIME2(dt, addHours, 10, HOUR(10));
TEST_TIME2(dt, addMinutes, 10, MINUTE(10));
TEST_TIME2(dt, addSeconds, 10, 10);
TEST_TIME2(dt, addHours, -10, HOUR(-10));
TEST_TIME2(dt, addMinutes, -10, MINUTE(-10));
TEST_TIME2(dt, addSeconds, -10, -10);
}
void DateTimeTest::datePart()
{
db.sampleTables()->query()->remove();
QDate d = QDate::currentDate();
auto s = Nut::create<SampleTable>();
s->setD(d);
db.sampleTables()->append(s);
db.saveChanges();
int count;
count = db.sampleTables()->query()->where(SampleTable::dField().year() == d.year())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dField().month() == d.month())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dField().day() == d.day())->count();
QTEST_ASSERT(count);
}
void DateTimeTest::timePart()
{
db.sampleTables()->query()->remove();
QTime t = QTime::currentTime();
auto s = Nut::create<SampleTable>();
s->setT(t);
db.sampleTables()->append(s);
db.saveChanges();
int count;
count = db.sampleTables()->query()->where(SampleTable::tField().hour() == t.hour())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::tField().minute() == t.minute())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::tField().second() == t.second())->count();
QTEST_ASSERT(count);
}
void DateTimeTest::dateTimePart()
{
db.sampleTables()->query()->remove();
QDateTime dt = QDateTime::currentDateTime();
auto s = Nut::create<SampleTable>();
s->setDT(dt);
db.sampleTables()->append(s);
db.saveChanges();
int count;
count = db.sampleTables()->query()->where(SampleTable::dtField().year() == dt.date().year())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dtField().month() == dt.date().month())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dtField().day() == dt.date().day())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dtField().hour() == dt.time().hour())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dtField().minute() == dt.time().minute())->count();
QTEST_ASSERT(count);
count = db.sampleTables()->query()->where(SampleTable::dtField().second() == dt.time().second())->count();
QTEST_ASSERT(count);
}
void DateTimeTest::cleanupTestCase()
{
db.sampleTables()->query()->remove();
db.close();
}
QTEST_MAIN(DateTimeTest)

View File

@ -0,0 +1,42 @@
#ifndef MAINTEST_H
#define MAINTEST_H
#include <QtCore/QObject>
#include <QtCore/qglobal.h>
#include <QDateTime>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#ifdef QT_GUI_LIB
#include <QColor>
#include <QPolygonF>
#endif
#include <QUrl>
#include <QUuid>
#include "db.h"
class DateTimeTest : public QObject
{
Q_OBJECT
DB db;
QDateTime _baseDateTime;
public:
explicit DateTimeTest(QObject *parent = nullptr);
signals:
private slots:
void initTestCase();
void dateAdd();
void timeAdd();
void dateTimeAdd();
void datePart();
void timePart();
void dateTimePart();
void cleanupTestCase();
};
#endif // MAINTEST_H

View File

@ -0,0 +1,20 @@
QT += testlib sql gui
TARGET = tst_datetime
TEMPLATE = app
CONFIG += warn_on c++11
include(../common/nut-lib.pri)
SOURCES += \
db.cpp \
sampletable.cpp \
tst_datetime.cpp
HEADERS += \
db.h \
sampletable.h \
tst_datetime.h
include($$PWD/../../ci-test-init.pri)

View File

@ -139,7 +139,12 @@ void GeneratorsTest::cleanupTestCase()
.arg(i.key(), i.value().sqlite, i.value().mysql,
i.value().psql, i.value().mssql));
}
qDebug() << p.toStdString().c_str();
QFile file(NUT_PATH "/doc/datatypes.md");
if (file.open(QIODevice::WriteOnly)) {
file.write(p.toUtf8());
file.close();
}
}
QTEST_MAIN(GeneratorsTest)