diff --git a/.travis.yml b/.travis.yml index 31fe73e..1502709 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ #Based on https://github.com/pcolby/libqtaws/blob/master/.travis.yml language: cpp +branches: + only: + - master + os: - linux - osx diff --git a/.vscode/ipch/38f51fe5930ec860/mmap_address.bin b/.vscode/ipch/38f51fe5930ec860/mmap_address.bin new file mode 100644 index 0000000..f015e03 Binary files /dev/null and b/.vscode/ipch/38f51fe5930ec860/mmap_address.bin differ diff --git a/3rdparty/serializer b/3rdparty/serializer index f9d81fc..e0bdec4 160000 --- a/3rdparty/serializer +++ b/3rdparty/serializer @@ -1 +1 @@ -Subproject commit f9d81fcc6244271b3926891b0c86554e1e6b967e +Subproject commit e0bdec4a7c93b4d4307fd89200000ff817a86c99 diff --git a/README.md b/README.md index 8eacca4..673d65f 100644 --- a/README.md +++ b/README.md @@ -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 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 diff --git a/ci-test-init.pri b/ci-test-init.pri index c3d1f7b..a24cc9d 100644 --- a/ci-test-init.pri +++ b/ci-test-init.pri @@ -1 +1,3 @@ #QT -= gui + +DEFINES += NUT_PATH=\\\"$$PWD/../../\\\" diff --git a/doc/database.md b/doc/database.md new file mode 100644 index 0000000..399ae72 --- /dev/null +++ b/doc/database.md @@ -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 + +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(this)) + , m_comments(new TableSet(this)) +{ +} +``` \ No newline at end of file diff --git a/doc/datatypes.md b/doc/datatypes.md new file mode 100644 index 0000000..a4bc9e7 --- /dev/null +++ b/doc/datatypes.md @@ -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) | diff --git a/doc/query.md b/doc/query.md new file mode 100644 index 0000000..ffb8d0c --- /dev/null +++ b/doc/query.md @@ -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\\>** 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 +``` +## 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() << 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(); +``` \ No newline at end of file diff --git a/doc/sharedpointer.md b/doc/sharedpointer.md new file mode 100644 index 0000000..85195f1 --- /dev/null +++ b/doc/sharedpointer.md @@ -0,0 +1,59 @@ +Nut can compile in *shared pointer* mode or *regular* mode + +In *shared pointer* mode reqults of queries is QList> and in *regular* mode results are QList + +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 + using RowList = QList>; + + template + using Row = QSharedPointer; +#else + template + using RowList = QList; + + template + using Row = T*; +#endif +``` + +In other words these types are defined by this table: + +| Mode | Nut::Row | Nut::RowList | +|------ |----- |--------- | +|Regular|T* | QList\ | +|Shared pointer|QSharedPointer\ | QList\\> | + +For the integration of your source, you can use these aliases. + +Ans also Nut::create() method are defined for two mode + +```cpp +#ifdef NUT_SHARED_POINTER + template + inline Row create(QObject *parent) { + return QSharedPointer(new T(parent)); + } +#else + template + inline Row 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(); +``` + +In above example if *NUT_SHARED_POINTER* is defined *post* is *QSharedPointer* else is *Post\** + +I recommand use *NUT_SHARED_POINTER* always! \ No newline at end of file diff --git a/doc/start.md b/doc/start.md new file mode 100644 index 0000000..74505c9 --- /dev/null +++ b/doc/start.md @@ -0,0 +1,45 @@ +### Read data from database: + +```cpp +auto posts = db.posts()->query() + ->where(Post::idField() == postId) + ->toList(); +// now posts is a QList 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(); +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(new Comment) in shared_pointer mode + + auto comment = Nut::create(); + 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!"); +} +``` + diff --git a/doc/table.md b/doc/table.md new file mode 100644 index 0000000..37e82cb --- /dev/null +++ b/doc/table.md @@ -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(this)), m_id(0), m_title("") +{ + +} +``` + +For more example take a look at _tests/common_ folder \ No newline at end of file diff --git a/src/defines.h b/src/defines.h index 144ae10..17d1032 100644 --- a/src/defines.h +++ b/src/defines.h @@ -87,12 +87,53 @@ public: \ NUT_DECLARE_FIELD(keytype, name##Id, read##Id, write##Id) \ NUT_INFO(__nut_FOREIGN_KEY, name, type) \ Nut::Row m_##name; \ -public: \ +public slots: \ Nut::Row read() const { return m_##name ; } \ - void write(Nut::Row name){ \ + Q_INVOKABLE void write(Nut::Row 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 m_##name; \ + keytype m_##name##Id; \ + Q_PROPERTY(Nut::Row name READ read WRITE write) \ + Q_PROPERTY(keytype name##Id READ read##Id WRITE write##Id) \ +public: \ + Nut::Row read() const; \ + void write(Nut::Row name); \ + static NUT_WRAP_NAMESPACE(FieldPhrase)& name##Id ## Field(){ \ + static NUT_WRAP_NAMESPACE(FieldPhrase) f = \ + NUT_WRAP_NAMESPACE(FieldPhrase) \ + (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 class::read() const { return m_##name ; } \ + void class::write(Nut::Row name){ \ + propertyChanged(QT_STRINGIFY2(name##Id)); \ + m_##name = name; \ + m_##name##Id = name->primaryValue().value(); \ + } \ + \ + keytype class::read##Id() const{ \ + if (m_##name) \ + return m_##name->primaryValue().value(); \ + 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) *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 row) { #endif +//template +//struct ForeignKeyData { +// Nut::Row _table; +// T _id; + +// ForeignKeyData() : _table(nullptr) +// {} + +// void setTable(Nut::Row t) { +// _table = t; +// _id = t->primaryValue().value(); +// } +// Nut::Row table() const { +// return _table; +// } +// void setValue(const T& val) { +// _table = nullptr; +// _id = val; +// } +// T value() const { +// if (_table) +// return _table->primaryValue().value(); +// return _id; +// } +//}; + NUT_END_NAMESPACE #endif // SYNTAX_DEFINES_H diff --git a/src/generators/mysqlgenerator.cpp b/src/generators/mysqlgenerator.cpp index c33af38..ea50b15 100644 --- a/src/generators/mysqlgenerator.cpp +++ b/src/generators/mysqlgenerator.cpp @@ -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((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 diff --git a/src/generators/mysqlgenerator.h b/src/generators/mysqlgenerator.h index 847b653..1609c81 100644 --- a/src/generators/mysqlgenerator.h +++ b/src/generators/mysqlgenerator.h @@ -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 &wheres, QList &orders, QList joins, int skip, int take); + QString createConditionalPhrase(const PhraseData *d) const override; private: bool readInsideParentese(QString &text, QString &out); }; diff --git a/src/generators/postgresqlgenerator.cpp b/src/generators/postgresqlgenerator.cpp index e1da1c2..83bc2c9 100644 --- a/src/generators/postgresqlgenerator.cpp +++ b/src/generators/postgresqlgenerator.cpp @@ -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((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); diff --git a/src/generators/sqlgeneratorbase.cpp b/src/generators/sqlgeneratorbase.cpp index 38300a3..74afe17 100644 --- a/src/generators/sqlgeneratorbase.cpp +++ b/src/generators/sqlgeneratorbase.cpp @@ -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 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 &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) diff --git a/src/generators/sqlgeneratorbase_p.h b/src/generators/sqlgeneratorbase_p.h index e6acf73..a25d3ee 100644 --- a/src/generators/sqlgeneratorbase_p.h +++ b/src/generators/sqlgeneratorbase_p.h @@ -162,6 +162,7 @@ protected: void replaceTableNames(QString &command); void removeTableNames(QString &command); + QString dateTimePartName(const PhraseData::Condition &op) const; }; NUT_END_NAMESPACE diff --git a/src/generators/sqlitegenerator.cpp b/src/generators/sqlitegenerator.cpp index e45168f..0555c47 100644 --- a/src/generators/sqlitegenerator.cpp +++ b/src/generators/sqlitegenerator.cpp @@ -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((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 diff --git a/src/generators/sqlitegenerator.h b/src/generators/sqlitegenerator.h index 402ae41..b0f2f0e 100644 --- a/src/generators/sqlitegenerator.h +++ b/src/generators/sqlitegenerator.h @@ -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 diff --git a/src/phrases/datephrase.cpp b/src/phrases/datephrase.cpp index a9c8d9b..0355377 100644 --- a/src/phrases/datephrase.cpp +++ b/src/phrases/datephrase.cpp @@ -20,3 +20,128 @@ #include "datephrase.h" +NUT_BEGIN_NAMESPACE + +FieldPhrase::FieldPhrase(const char *className, const char *s) : + AbstractFieldPhrase(className, s) +{} + +ConditionalPhrase FieldPhrase::addYears(int years) { + return ConditionalPhrase(this, PhraseData::AddYears, years); +} + +ConditionalPhrase FieldPhrase::addMonths(int months) { + return ConditionalPhrase(this, PhraseData::AddMonths, months); +} + +ConditionalPhrase FieldPhrase::addDays(int days) { + return ConditionalPhrase(this, PhraseData::AddDays, days); +} + +ConditionalPhrase FieldPhrase::year() { + return ConditionalPhrase(this, PhraseData::DatePartYear); +} + +ConditionalPhrase FieldPhrase::month() { + return ConditionalPhrase(this, PhraseData::DatePartMonth); +} + +ConditionalPhrase FieldPhrase::day() { + return ConditionalPhrase(this, PhraseData::DatePartDay); +} + +FieldPhrase::FieldPhrase(const char *className, const char *s) : + AbstractFieldPhrase(className, s) +{} + + +ConditionalPhrase FieldPhrase::addHours(int hours) { + return ConditionalPhrase(this, PhraseData::AddHours, hours); +} + +ConditionalPhrase FieldPhrase::addMinutes(int minutes) { + return ConditionalPhrase(this, PhraseData::AddMinutes, minutes); +} + +ConditionalPhrase FieldPhrase::addSeconds(int seconds) { + return ConditionalPhrase(this, PhraseData::AddSeconds, seconds); +} + +ConditionalPhrase FieldPhrase::hour() { + return ConditionalPhrase(this, PhraseData::DatePartHour); +} + +ConditionalPhrase FieldPhrase::minute() { + return ConditionalPhrase(this, PhraseData::DatePartMinute); +} + +ConditionalPhrase FieldPhrase::second() { + return ConditionalPhrase(this, PhraseData::DatePartSecond); +} + +ConditionalPhrase FieldPhrase::msec() { + return ConditionalPhrase(this, PhraseData::DatePartMilisecond); +} + +FieldPhrase::FieldPhrase(const char *className, const char *s) : + AbstractFieldPhrase(className, s) +{} + +ConditionalPhrase FieldPhrase::addYears(int years) { + return ConditionalPhrase(this, PhraseData::AddYearsDateTime, years); +} + +ConditionalPhrase FieldPhrase::addMonths(int months) { + return ConditionalPhrase(this, PhraseData::AddMonthsDateTime, months); +} + +ConditionalPhrase FieldPhrase::addDays(int days) { + return ConditionalPhrase(this, PhraseData::AddDaysDateTime, days); +} + +ConditionalPhrase FieldPhrase::addHours(int hours) { + return ConditionalPhrase(this, PhraseData::AddHoursDateTime, hours); +} + +ConditionalPhrase FieldPhrase::addMinutes(int minutes) { + return ConditionalPhrase(this, PhraseData::AddMinutesDateTime, minutes); +} + +ConditionalPhrase FieldPhrase::addSeconds(int seconds) { + return ConditionalPhrase(this, PhraseData::AddSecondsDateTime, seconds); +} + +ConditionalPhrase FieldPhrase::year() { + return ConditionalPhrase(this, PhraseData::DatePartYear); +} + +ConditionalPhrase FieldPhrase::month() { + return ConditionalPhrase(this, PhraseData::DatePartMonth); +} + +ConditionalPhrase FieldPhrase::day() { + return ConditionalPhrase(this, PhraseData::DatePartDay); +} + +ConditionalPhrase FieldPhrase::hour() { + return ConditionalPhrase(this, PhraseData::DatePartHour); +} + +ConditionalPhrase FieldPhrase::minute() { + return ConditionalPhrase(this, PhraseData::DatePartMinute); +} + +ConditionalPhrase FieldPhrase::second() { + return ConditionalPhrase(this, PhraseData::DatePartSecond); +} + +ConditionalPhrase FieldPhrase::msec() { + return ConditionalPhrase(this, PhraseData::DatePartMilisecond); +} + + +COMMON_OPERATORS_IMPL(QDate) +COMMON_OPERATORS_IMPL(QTime) +COMMON_OPERATORS_IMPL(QDateTime) + +NUT_END_NAMESPACE diff --git a/src/phrases/datephrase.h b/src/phrases/datephrase.h index 5db2186..8d3dbd3 100644 --- a/src/phrases/datephrase.h +++ b/src/phrases/datephrase.h @@ -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 -struct __is_date_helper - : public std::false_type { }; +#define COMMON_OPERATORS_IMPL(T) \ + AssignmentPhrase FieldPhrase::operator =(const T &other) { \ + return AssignmentPhrase(this, other); \ + } \ + ConditionalPhrase FieldPhrase::operator <(const QVariant &other) { \ + return ConditionalPhrase(this, PhraseData::Less, other); \ + } \ + ConditionalPhrase FieldPhrase::operator <=(const QVariant &other) { \ + return ConditionalPhrase(this, PhraseData::LessEqual, other); \ + } \ + ConditionalPhrase FieldPhrase::operator >(const QVariant &other) { \ + return ConditionalPhrase(this, PhraseData::Greater, other); \ + } \ + ConditionalPhrase FieldPhrase::operator >=(const QVariant &other) { \ + return ConditionalPhrase(this, PhraseData::GreaterEqual, other); \ + } \ + ConditionalPhrase FieldPhrase::between(const QVariant &min, const QVariant &max) \ + { \ + return ConditionalPhrase(this, PhraseData::Between, \ + QVariantList() << min << max); \ + } template<> -struct __is_date_helper - : public std::true_type { }; - -template<> -struct __is_date_helper - : public std::true_type { }; - -template<> -struct __is_date_helper - : public std::true_type { }; - -template -struct is_date - : public __is_date_helper::type>::type -{ }; - - -template -inline bool is_valid_template() {return false;} - -template <> -inline bool is_valid_template() {return true;} - -template <> -inline bool is_valid_template() {return true;} - -template <> -inline bool is_valid_template() {return true;} - -template <> -inline bool is_valid_template() {return true;} - -template -class NUT_EXPORT FieldPhrase::value>::type> - : public AbstractFieldPhrase +class NUT_EXPORT FieldPhrase : 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::value, int>::type = 0> - ConditionalPhrase addYears(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddYears, val); - } - ConditionalPhrase addMonths(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddMonths, val); - } - ConditionalPhrase addDays(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddDays, val); - } +template<> +class NUT_EXPORT FieldPhrase : public AbstractFieldPhrase +{ +public: + FieldPhrase(const char *className, const char *s); - ConditionalPhrase addHours(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddHours, val); - } - ConditionalPhrase addMinutes(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddMinutes, val); - } - ConditionalPhrase addSeconds(int val) { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::AddSeconds, val); - } + COMMON_OPERATORS_DECL(QTime) - ConditionalPhrase year() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartYear); - } - ConditionalPhrase month() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartMonth); - } - ConditionalPhrase day() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartDay); - } - ConditionalPhrase hour() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartHour); - } - ConditionalPhrase minute() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartMinute); - } - ConditionalPhrase second() { - if (!is_valid_template()) - return ConditionalPhrase(); - return ConditionalPhrase(this, PhraseData::DatePartSecond); - } - ConditionalPhrase msec() { - if (!is_valid_template()) - 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 : 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 diff --git a/src/phrases/phrasedata.h b/src/phrases/phrasedata.h index d1ae6f0..a773cb6 100644 --- a/src/phrases/phrasedata.h +++ b/src/phrases/phrasedata.h @@ -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, diff --git a/src/query.h b/src/query.h index b535df3..ca1a7f5 100644 --- a/src/query.h +++ b/src/query.h @@ -289,11 +289,13 @@ Q_OUTOFLINE_TEMPLATE RowList Query::toList(int count) //create table row Table *table; + Row shp; if (data.table->className() == d->className) { table = new T(); #ifdef NUT_SHARED_POINTER - auto shp = QSharedPointer(qobject_cast(table)); + shp = QSharedPointer(qobject_cast(table)); returnList.append(shp); + d->tableSet->add(shp); #else returnList.append(dynamic_cast(table)); #endif @@ -317,12 +319,27 @@ Q_OUTOFLINE_TEMPLATE RowList Query::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(shp)); +#endif } table->setStatus(Table::FeatchedFromDB); @@ -330,7 +347,7 @@ Q_OUTOFLINE_TEMPLATE RowList Query::toList(int count) table->clear(); //set last created row - data.lastRow = table; + data.lastRow = /*QSharedPointer
*/(table); } //while } // while diff --git a/src/sqlmodel.cpp b/src/sqlmodel.cpp index 300b89e..fa4b698 100644 --- a/src/sqlmodel.cpp +++ b/src/sqlmodel.cpp @@ -73,27 +73,10 @@ QVariant SqlModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole) { Row
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
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(); diff --git a/src/table.h b/src/table.h index d661784..16dfbd3 100644 --- a/src/table.h +++ b/src/table.h @@ -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); diff --git a/src/tablesetbase.cpp b/src/tablesetbase.cpp index 1cf0b0d..8ee15a0 100644 --- a/src/tablesetbase.cpp +++ b/src/tablesetbase.cpp @@ -42,6 +42,9 @@ TableSetBase::~TableSetBase() { foreach (Table *t, data->tables) t->setParentTableSet(nullptr); + + foreach (Row
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
t) +{ + data.detach(); + data->childs.append(t); +} + +void TableSetBase::remove(Row
t) +{ + data.detach(); + data->childs.removeOne(t); +} + QString TableSetBase::childClassName() const { return data->childClassName; diff --git a/src/tablesetbase_p.h b/src/tablesetbase_p.h index cb8c6d2..5911f42 100644 --- a/src/tablesetbase_p.h +++ b/src/tablesetbase_p.h @@ -57,10 +57,13 @@ protected: // QString _childClassName; QExplicitlySharedDataPointer data; -private: +public://TODO: change this to private void add(Table* t); void remove(Table *t); + void add(Row
t); + void remove(Row
t); + friend class Table; friend class QueryBase; }; diff --git a/test/common/comment.cpp b/test/common/comment.cpp index d732145..59b2e0d 100644 --- a/test/common/comment.cpp +++ b/test/common/comment.cpp @@ -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) diff --git a/test/common/comment.h b/test/common/comment.h index 437aa20..e7628ab 100644 --- a/test/common/comment.h +++ b/test/common/comment.h @@ -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); diff --git a/test/common/score.cpp b/test/common/score.cpp index 08466a2..91881b5 100644 --- a/test/common/score.cpp +++ b/test/common/score.cpp @@ -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) diff --git a/test/common/score.h b/test/common/score.h index 72d907b..f8c3579 100644 --- a/test/common/score.h +++ b/test/common/score.h @@ -1,6 +1,7 @@ #ifndef SCORE_H #define SCORE_H +#include #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); diff --git a/test/test.pro b/test/test.pro index 504a9c5..1a7a38e 100644 --- a/test/test.pro +++ b/test/test.pro @@ -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 diff --git a/test/tst_basic/tst_basic.cpp b/test/tst_basic/tst_basic.cpp index f728887..55d59c3 100644 --- a/test/tst_basic/tst_basic.cpp +++ b/test/tst_basic/tst_basic.cpp @@ -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(); diff --git a/test/tst_datetime/db.cpp b/test/tst_datetime/db.cpp new file mode 100644 index 0000000..e570806 --- /dev/null +++ b/test/tst_datetime/db.cpp @@ -0,0 +1,9 @@ +#include "db.h" + +#include "sampletable.h" + +DB::DB() : Nut::Database (), + m_sampleTables(new Nut::TableSet(this)) +{ + +} diff --git a/test/tst_datetime/db.h b/test/tst_datetime/db.h new file mode 100644 index 0000000..401ac8c --- /dev/null +++ b/test/tst_datetime/db.h @@ -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 diff --git a/test/tst_datetime/sampletable.cpp b/test/tst_datetime/sampletable.cpp new file mode 100644 index 0000000..68a9c9d --- /dev/null +++ b/test/tst_datetime/sampletable.cpp @@ -0,0 +1,6 @@ +#include "sampletable.h" + +SampleTable::SampleTable(QObject *parent) : Nut::Table (parent) +{ + +} diff --git a/test/tst_datetime/sampletable.h b/test/tst_datetime/sampletable.h new file mode 100644 index 0000000..2587bac --- /dev/null +++ b/test/tst_datetime/sampletable.h @@ -0,0 +1,27 @@ +#ifndef SAMPLETABLE_H +#define SAMPLETABLE_H + +#include +#include +#include + +#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 diff --git a/test/tst_datetime/tst_datetime.cpp b/test/tst_datetime/tst_datetime.cpp new file mode 100644 index 0000000..5f1fd53 --- /dev/null +++ b/test/tst_datetime/tst_datetime.cpp @@ -0,0 +1,211 @@ +#include +#include +#include +#include + +#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(); \ + 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(); \ + 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(); \ + 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(); \ + 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(); + 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(); + 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(); + 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) diff --git a/test/tst_datetime/tst_datetime.h b/test/tst_datetime/tst_datetime.h new file mode 100644 index 0000000..9f309c2 --- /dev/null +++ b/test/tst_datetime/tst_datetime.h @@ -0,0 +1,42 @@ +#ifndef MAINTEST_H +#define MAINTEST_H + +#include +#include + +#include +#include +#include +#include +#ifdef QT_GUI_LIB +#include +#include +#endif +#include +#include + +#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 diff --git a/test/tst_datetime/tst_datetime.pro b/test/tst_datetime/tst_datetime.pro new file mode 100644 index 0000000..7a9a327 --- /dev/null +++ b/test/tst_datetime/tst_datetime.pro @@ -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) diff --git a/test/tst_generators/tst_generators.cpp b/test/tst_generators/tst_generators.cpp index 285cd05..8eb4d5f 100644 --- a/test/tst_generators/tst_generators.cpp +++ b/test/tst_generators/tst_generators.cpp @@ -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)