initial commit

This commit is contained in:
silverqx
2020-12-04 09:42:18 +01:00
commit c9a7521445
56 changed files with 5837 additions and 0 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[**.yml]
indent_size = 2

7
.gitattributes vendored Normal file
View File

@@ -0,0 +1,7 @@
core.eol=lf
* text eol=lf
*.zip binary
*.png binary
*.ico binary
*.qm binary
*.icns binary

73
.gitignore vendored Normal file
View File

@@ -0,0 +1,73 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
*.res
*.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
*.pyc
# Binaries
# --------
*.dll
*.exe

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Silver Zachara
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

0
NOTES.txt Normal file
View File

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# TinyOrm
TinyOrm is ORM heavily inspired by Laravel Eloquent ORM, currently in development and may be stays forever 🙂.

115
TinyOrm.pro Normal file
View File

@@ -0,0 +1,115 @@
QT -= gui
QT += sql
# Configuration
# ---
CONFIG += c++2a strict_c++ console silent
CONFIG -= c++11 app_bundle
# Some info output
# ---
CONFIG(debug, debug|release): message( "Project is built in DEBUG mode." )
CONFIG(release, debug|release): message( "Project is built in RELEASE mode." )
# Disable debug output in release mode
CONFIG(release, debug|release) {
message( "Disabling debug output." )
DEFINES += QT_NO_DEBUG_OUTPUT
}
# TinyOrm defines
# ---
DEFINES += PROJECT_TINYORM
# Log queries with time measurement
DEFINES += MANGO_DEBUG_SQL
# Qt defines
# ---
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
#DEFINES += QT_NO_CAST_FROM_ASCII
#DEFINES += QT_RESTRICTED_CAST_FROM_ASCII
DEFINES += QT_NO_CAST_TO_ASCII
DEFINES += QT_NO_CAST_FROM_BYTEARRAY
DEFINES += QT_USE_QSTRINGBUILDER
DEFINES += QT_STRICT_ITERATORS
# WinApi
# ---
# Windows 10 1903 "19H1" - 0x0A000007
DEFINES += NTDDI_VERSION=0x0A000007
# Windows 10 - 0x0A00
DEFINES += _WIN32_WINNT=0x0A00
DEFINES += _WIN32_IE=0x0A00
DEFINES += UNICODE
DEFINES += _UNICODE
DEFINES += WIN32
DEFINES += _WIN32
DEFINES += WIN32_LEAN_AND_MEAN
DEFINES += NOMINMAX
win32-msvc* {
# I don't use -MP flag, because using jom
QMAKE_CXXFLAGS += -guard:cf -permissive- -Zc:ternary
QMAKE_LFLAGS += /guard:cf
QMAKE_LFLAGS_RELEASE += /OPT:REF /OPT:ICF=5
}
win32-g++* {
}
else:win32-msvc* {
# MySQL C library is used by ORM and it uses mysql_ping()
INCLUDEPATH += $$quote(C:/Program Files/MySQL/MySQL Server 8.0/include)
# range-v3
INCLUDEPATH += $$quote(E:/c/qMedia/vcpkg/installed/x64-windows/include)
# boost
INCLUDEPATH += $$quote(E:/c_libs/boost/boost_latest)
LIBS += $$quote(-LC:/Program Files/MySQL/MySQL Server 8.0/lib)
LIBS += libmysql.lib
}
# File version and windows manifest
# ---
win32:VERSION = 0.1.0.0
else:VERSION = 0.1.0
win32-msvc* {
QMAKE_TARGET_PRODUCT = TinyOrm
QMAKE_TARGET_DESCRIPTION = TinyOrm user friendly ORM
QMAKE_TARGET_COMPANY = Crystal Studio
QMAKE_TARGET_COPYRIGHT = Copyright (©) 2020 Crystal Studio
# RC_ICONS = images/qMedia.ico
RC_LANG = 1033
}
# Use Precompiled headers (PCH)
# ---
INCLUDEPATH += $$PWD
PRECOMPILED_HEADER = pch.h
precompile_header:!isEmpty(PRECOMPILED_HEADER) {
DEFINES += USING_PCH
}
HEADERS += pch.h
# Application source files
# ---
SOURCES += main.cpp
include(src/src.pri)

18
main.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include <QDebug>
#include <qt_windows.h>
#include "testorm.h"
int main(int, char *[])
{
// SetConsoleOutputCP(CP_UTF8);
SetConsoleOutputCP(1250);
qDebug() << "";
TestOrm testOrm;
testOrm.run();
qDebug() << "\n";
}

46
pch.h Normal file
View File

@@ -0,0 +1,46 @@
/* This file can't be included in the project, it's for a precompiled header. */
/* Add C includes here */
#if defined __cplusplus
/* Add C++ includes here */
#include <QCoreApplication>
#include <QDir>
#include <QHash>
#include <QMap>
#include <QPointer>
#include <QRegularExpression>
#include <QSharedPointer>
#include <QStringList>
#include <QTimer>
#include <QVariant>
#include <QVector>
#include <array>
#include <bitset>
#include <algorithm>
#include <cassert>
#include <cfloat>
#include <cmath>
#include <complex>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <initializer_list>
#include <iterator>
#include <map>
#include <memory>
#include <mutex>
#include <numeric>
#include <optional>
#include <set>
#include <span>
#include <string>
#include <thread>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <vector>
#endif

View File

@@ -0,0 +1,358 @@
#include "databaseconnection.h"
#include <QDebug>
#include <QElapsedTimer>
#include <QtSql/QSqlDriver>
#include <QtSql/QSqlError>
#include "mysql.h"
#include "orm/logquery.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
const char *DatabaseConnection::CONNECTION_NAME = const_cast<char *>("crystal");
const char *DatabaseConnection::SAVEPOINT_NAMESPACE = const_cast<char *>("export");
/*!
\class DatabaseConnection
\brief The DatabaseConnection class handles a connection to the database.
\ingroup database
\inmodule Export
Wrapper around QSqlDatabase class, many methods are only proxies with
some error handling.
Savepoints (nested transactions) are not managed automatically like eg
in Laravel's Eloquent ORM, because I want to be more explicit, so when
I need to start Savepoint, I will call savepoint() method and not
transcation(). The same is true for rollback(), so I will not call
rollback() for both, to end transaction and to end savepoint, instead,
I will call rollback() for transaction and rollbackToSavepoint("xx_1")
for savepoint. This makes it clear at a glance what is happening.
*/
namespace
{
// TODO duplicate, merge with logExecutedQuery() silverqx
const auto logQuery = [](const QSqlQuery &query,
const std::optional<quint64> elapsed = std::nullopt)
{
qDebug().nospace().noquote()
<< "Executed prepared query (" << (elapsed ? *elapsed : -1) << "ms, "
<< query.size() << " results, " << query.numRowsAffected()
<< " affected) : " << parseExecutedQuery(query);
};
}
DatabaseConnection *DatabaseConnection::m_instance = nullptr;
DatabaseConnection::DatabaseConnection()
{
auto db = QSqlDatabase::addDatabase("QMYSQL", CONNECTION_NAME);
db.setHostName("127.0.0.1");
//#ifdef QT_DEBUG
db.setDatabaseName("");
// db.setDatabaseName("");
//#else
// db.setDatabaseName("");
//#endif
db.setUserName("");
db.setPassword("");
if (db.open())
return;
qDebug() << "Connect to DB failed :"
<< db.lastError().text();
}
DatabaseConnection &DatabaseConnection::instance()
{
if (!m_instance)
m_instance = new DatabaseConnection();
return *m_instance;
}
void DatabaseConnection::freeInstance()
{
if (!m_instance)
return;
delete m_instance;
m_instance = nullptr;
}
QSqlQuery DatabaseConnection::query() const
{
return QSqlQuery(database());
}
QSharedPointer<QueryBuilder> DatabaseConnection::queryBuilder() const
{
return QSharedPointer<QueryBuilder>::create(*this, m_grammar);
}
QSqlDatabase DatabaseConnection::database()
{
return QSqlDatabase::database(CONNECTION_NAME);
}
bool DatabaseConnection::pingDatabase()
{
auto db = database();
const auto getMysqlHandle = [&db]() -> MYSQL *
{
auto driverHandle = db.driver()->handle();
if (qstrcmp(driverHandle.typeName(), "MYSQL*") == 0)
return *static_cast<MYSQL **>(driverHandle.data());
return nullptr;
};
const auto mysqlPing = [getMysqlHandle]()
{
auto mysqlHandle = getMysqlHandle();
if (mysqlHandle == nullptr)
return false;
auto ping = mysql_ping(mysqlHandle);
auto errNo = mysql_errno(mysqlHandle);
/* So strange logic, because I want interpret CR_COMMANDS_OUT_OF_SYNC errno as
successful ping. */
if ((ping != 0) && (errNo == CR_COMMANDS_OUT_OF_SYNC)) {
// TODO log to file, how often this happen silverqx
qWarning("mysql_ping() returned : CR_COMMANDS_OUT_OF_SYNC(%ud)", errNo);
return true;
}
else if (ping == 0)
return true;
else if (ping != 0)
return false;
else {
qWarning() << "Unknown behavior during mysql_ping(), this should never happen :/";
return false;
}
};
if (db.isOpen() && mysqlPing()) {
showDbConnected();
return true;
}
// The connection was lost here
showDbDisconnected();
// Database connection have to be closed manually
// isOpen() check is called in MySQL driver
db.close();
// Reset in transaction state
m_inTransaction = false;
// Reset the savepoints counter
m_savepoints = 0;
return false;
}
bool DatabaseConnection::transaction()
{
Q_ASSERT(m_inTransaction == false);
const auto ok = database().transaction();
if (!ok)
return false;
m_inTransaction = true;
return true;
}
bool DatabaseConnection::commit()
{
Q_ASSERT(m_inTransaction);
const auto ok = database().commit();
if (!ok)
return false;
m_inTransaction = false;
return true;
}
bool DatabaseConnection::rollback()
{
Q_ASSERT(m_inTransaction);
const auto ok = database().rollback();
if (!ok)
return false;
m_inTransaction = false;
return true;
}
bool DatabaseConnection::savepoint(const QString &id)
{
// TODO rewrite savepoint() and rollback() with a new m_db.statement() API silverqx
Q_ASSERT(m_inTransaction);
auto savePoint = query();
const auto query = QStringLiteral("SAVEPOINT %1_%2").arg(SAVEPOINT_NAMESPACE, id);
// Execute a savepoint query
const auto ok = savePoint.exec(query);
Q_ASSERT_X(ok,
"DatabaseConnection::savepoint(QString &id)",
query.toUtf8().constData());
if (!ok) {
const auto savepointId = QStringLiteral("%1_%2").arg(SAVEPOINT_NAMESPACE, id);
qDebug() << "Savepoint" << savepointId << "failed :"
<< savePoint.lastError().text();
}
++m_savepoints;
return ok;
}
bool DatabaseConnection::rollbackToSavepoint(const QString &id)
{
Q_ASSERT(m_inTransaction);
Q_ASSERT(m_savepoints > 0);
auto rollbackToSavepoint = query();
const auto query = QStringLiteral("ROLLBACK TO SAVEPOINT %1_%2")
.arg(SAVEPOINT_NAMESPACE, id);
// Execute a rollback to savepoint query
const auto ok = rollbackToSavepoint.exec(query);
Q_ASSERT_X(ok,
"DatabaseConnection::rollbackToSavepoint(QString &id)",
query.toUtf8().constData());
if (!ok) {
const auto savepointId = QStringLiteral("%1_%2").arg(SAVEPOINT_NAMESPACE, id);
qDebug() << "Rollback to Savepoint" << savepointId << "failed :"
<< rollbackToSavepoint.lastError().text();
}
m_savepoints = std::max<int>(0, m_savepoints - 1);
return ok;
}
std::tuple<bool, QSqlQuery>
DatabaseConnection::select(const QString &queryString,
const QVector<QVariant> &bindings) const
{
return statement(queryString, bindings);
}
std::tuple<bool, QSqlQuery>
DatabaseConnection::insert(const QString &queryString,
const QVector<QVariant> &bindings) const
{
return statement(queryString, bindings);
}
std::tuple<int, QSqlQuery>
DatabaseConnection::update(const QString &queryString,
const QVector<QVariant> &bindings) const
{
return affectingStatement(queryString, bindings);
}
std::tuple<int, QSqlQuery>
DatabaseConnection::remove(const QString &queryString,
const QVector<QVariant> &bindings) const
{
return affectingStatement(queryString, bindings);
}
std::tuple<bool, QSqlQuery>
DatabaseConnection::statement(const QString &queryString,
const QVector<QVariant> &bindings) const
{
auto query = prepareQuery(queryString, bindings);
#ifdef MANGO_DEBUG_SQL
QElapsedTimer timer;
timer.start();
#endif
const auto ok = query.exec();
if (!ok) {
qDebug() << "Statement in DatabaseConnection::statement() failed :"
<< query.lastError().text();
logQuery(query);
}
#ifdef MANGO_DEBUG_SQL
else
logQuery(query, timer.elapsed());
#endif
return {ok, query};
}
std::tuple<int, QSqlQuery>
DatabaseConnection::affectingStatement(const QString &queryString,
const QVector<QVariant> &bindings) const
{
auto query = prepareQuery(queryString, bindings);
#ifdef MANGO_DEBUG_SQL
QElapsedTimer timer;
timer.start();
#endif
const auto ok = query.exec();
if (!ok) {
qDebug() << "Affecting statement in DatabaseConnection::affectingStatement() failed :"
<< query.lastError().text();
logQuery(query);
}
#ifdef MANGO_DEBUG_SQL
else
logQuery(query, timer.elapsed());
#endif
return {query.numRowsAffected(), query};
}
void DatabaseConnection::showDbDisconnected()
{
if (m_dbDisconnectedShowed)
return;
m_dbDisconnectedShowed = true;
// Reset connected flag
m_dbConnectedShowed = false;
qWarning() << "No active database connection, torrent additions / removes will "
"not be commited";
}
void DatabaseConnection::showDbConnected()
{
if (m_dbConnectedShowed)
return;
m_dbConnectedShowed = true;
// Reset disconnected flag
m_dbDisconnectedShowed = false;
qInfo() << "Database connected";
}
QSqlQuery DatabaseConnection::prepareQuery(const QString &queryString,
const QVector<QVariant> &bindings) const
{
// Prepare query string
auto query = this->query();
// TODO solve setForwardOnly() in DatabaseConnection class, again this problem 🤔 silverqx
// query.setForwardOnly(m_forwardOnly);
query.prepare(queryString);
// Prepare query bindings
auto itBinding = bindings.constBegin();
while (itBinding != bindings.constEnd()) {
query.addBindValue(*itBinding);
++itBinding;
}
return query;
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,101 @@
#ifndef DATABASECONNECTION_H
#define DATABASECONNECTION_H
#include <QtSql/QSqlDatabase>
#include "orm/grammar.h"
#include "orm/query/querybuilder.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class DatabaseConnection final
{
Q_DISABLE_COPY(DatabaseConnection)
public:
static DatabaseConnection &instance();
static void freeInstance();
/*! Return underlying database connection. */
static QSqlDatabase database();
QSqlQuery query() const;
QSharedPointer<QueryBuilder> queryBuilder() const;
/*! Check database connection and show warnings when the state changed. */
bool pingDatabase();
bool transaction();
bool commit();
bool rollback();
bool savepoint(const QString &id);
bool rollbackToSavepoint(const QString &id);
/*! Run a select statement against the database. */
std::tuple<bool, QSqlQuery>
select(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Run an insert statement against the database. */
std::tuple<bool, QSqlQuery>
insert(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Run an update statement against the database. */
std::tuple<int, QSqlQuery>
update(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Run a delete statement against the database. */
std::tuple<int, QSqlQuery>
remove(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Execute an SQL statement and return the boolean result and QSqlQuery. */
std::tuple<bool, QSqlQuery>
statement(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Run an SQL statement and get the number of rows affected. */
std::tuple<int, QSqlQuery>
affectingStatement(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
/*! Get a new raw query expression. */
inline Expression raw(const QVariant &value) const
{ return value; }
/*! Get the database connection name. */
inline const QString getName() const
{ return CONNECTION_NAME; }
private:
static DatabaseConnection *m_instance;
DatabaseConnection();
~DatabaseConnection() = default;
static const char *CONNECTION_NAME;
static const char *SAVEPOINT_NAMESPACE;
void showDbDisconnected();
void showDbConnected();
/*! Prepare an SQL statement and return the query object. */
QSqlQuery prepareQuery(const QString &queryString,
const QVector<QVariant> &bindings = {}) const;
bool m_dbDisconnectedShowed = false;
bool m_dbConnectedShowed = false;
/*! The connection is in the transaction state. */
bool m_inTransaction = false;
/*! Active savepoints counter. */
uint m_savepoints = 0;
Grammar m_grammar;
};
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // DATABASECONNECTION_H

88
src/orm/entitymanager.cpp Normal file
View File

@@ -0,0 +1,88 @@
#include "entitymanager.h"
#include <QtSql/QSqlQuery>
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
/*!
\class EntityManager
\brief The EntityManager class manages repositories and a connection
to the database.
\ingroup database
\inmodule Export
EntityManager is the base class to work with the database, it creates
and manages repository classes by helping with the RepositoryFactory
class.
Creates the database connection which is represented by
DatabaseConnection class.
EntityManager should be used in controllers ( currently TorrentExporter
is like a controller class ), services, and repository classes to access
the database. There is no need to use the QSqlDatabase or the
DatabaseConnection classes directly.
EntityManager is also injected into a repository and a service
classes constructors.
The circular dependency problem is solved by including entitymanager.h
in the baserepository.h file.
*/
EntityManager::EntityManager()
: m_db(DatabaseConnection::instance())
, m_repositoryFactory(*this)
{}
EntityManager::~EntityManager()
{
DatabaseConnection::freeInstance();
}
QSqlQuery EntityManager::query() const
{
return m_db.query();
}
QSharedPointer<QueryBuilder> EntityManager::queryBuilder() const
{
return m_db.queryBuilder();
}
bool EntityManager::pingDatabase()
{
return m_db.pingDatabase();
}
bool EntityManager::transaction()
{
return m_db.transaction();
}
bool EntityManager::commit()
{
return m_db.commit();
}
bool EntityManager::rollback()
{
return m_db.rollback();
}
bool EntityManager::savepoint(const QString &id)
{
return m_db.savepoint(id);
}
bool EntityManager::rollbackToSavepoint(const QString &id)
{
return m_db.rollbackToSavepoint(id);
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

52
src/orm/entitymanager.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef ENTITYMANAGER_H
#define ENTITYMANAGER_H
#include "orm/databaseconnection.h"
#include "orm/repositoryfactory.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class EntityManager final
{
Q_DISABLE_COPY(EntityManager)
public:
EntityManager();
~EntityManager();
template<typename Repository>
QSharedPointer<Repository> getRepository() const;
/*! Create a new QSqlQuery. */
QSqlQuery query() const;
QSharedPointer<QueryBuilder> queryBuilder() const;
/*! Check database connection and show warnings when the state changed. */
bool pingDatabase();
bool transaction();
bool commit();
bool rollback();
bool savepoint(const QString &id);
bool rollbackToSavepoint(const QString &id);
private:
DatabaseConnection &m_db;
RepositoryFactory m_repositoryFactory;
};
template<typename Repository>
QSharedPointer<Repository> EntityManager::getRepository() const
{
return m_repositoryFactory.getRepository<Repository>();
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // ENTITYMANAGER_H

22
src/orm/expression.cpp Normal file
View File

@@ -0,0 +1,22 @@
#include "expression.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
Expression::Expression(const QVariant &value)
: m_value(value)
{}
Expression::operator QVariant() const
{
return QVariant::fromValue(*this);
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

42
src/orm/expression.h Normal file
View File

@@ -0,0 +1,42 @@
#ifndef EXPRESSION_H
#define EXPRESSION_H
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
// TODO rework saveing Expressions to the "BindingsMap m_bindings", see also todo at BindingsMap definition in ormtypes.h silverqx
class Expression
{
public:
Expression() = default;
~Expression() = default;
Expression(const QVariant &value);
Expression(const Expression &) = default;
Expression &operator=(const Expression &) = default;
operator QVariant() const;
inline QVariant getValue() const
{ return m_value; }
private:
QVariant m_value;
};
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#ifdef MANGO_COMMON_NAMESPACE
Q_DECLARE_METATYPE(MANGO_COMMON_NAMESPACE::Orm::Expression);
#else
Q_DECLARE_METATYPE(Orm::Expression);
#endif
#endif // EXPRESSION_H

535
src/orm/grammar.cpp Normal file
View File

@@ -0,0 +1,535 @@
#include "grammar.h"
#include <QRegularExpression>
#include "orm/query/joinclause.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
using JoinClause = Query::JoinClause;
namespace
{
// TODO duplicate in moviedetailservice.cpp silverqx
/*! Join container by delimiter and exclude empty or null values. */
const auto joinContainer =
[](const auto &container, const QString &delimiter) -> QString
{
QString result = "";
int count = 0;
for (const auto &value : container) {
if (value.isEmpty() || value.isNull())
continue;
result += value + delimiter;
++count;
}
if (count > 0)
result.chop(delimiter.size());
return result;
};
}
QString Grammar::compileSelect(const QueryBuilder &query) const
{
return joinContainer(compileComponents(query), QStringLiteral(" ")).trimmed();
}
QString Grammar::compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values) const
{
return compileInsert(query, values, false);
}
QString Grammar::compileInsertOrIgnore(const QueryBuilder &query,
const QVector<QVariantMap> &values) const
{
return compileInsert(query, values, true);
}
QString Grammar::compileUpdate(const QueryBuilder &query, const QVector<UpdateItem> &values) const
{
const auto table = query.getTable();
const auto columns = compileUpdateColumns(values);
const auto wheres = compileWheres(query);
return query.getJoins().isEmpty() ? compileUpdateWithoutJoins(table, columns, wheres)
: compileUpdateWithJoins(query, table, columns, wheres);
}
namespace
{
/*! Flat bindings map and exclude select and join bindings. */
const auto flatBindingsForUpdate = [](BindingsMap &bindings)
{
QVector<BindingType> bindingsToReject {BindingType::SELECT, BindingType::JOIN};
QVector<std::reference_wrapper<const QVariant>> cleanBindingsFlatten;
for (auto itBindingVector = bindings.constBegin(); itBindingVector != bindings.constEnd();
++itBindingVector)
{
if (bindingsToReject.contains(itBindingVector.key()))
continue;
for (const auto &binding : itBindingVector.value())
cleanBindingsFlatten.append(std::cref(binding));
}
return cleanBindingsFlatten;
};
/*! Merge a 'from' vector into a 'to' vector. */
const auto mergeVector = [](auto &to, const auto &from)
{
std::copy(from.cbegin(), from.cend(), std::back_inserter(to));
};
/*! Merge update values bindings. */
const auto mergeValuesForUpdate = [](auto &to, const auto &from)
{
std::for_each(from.cbegin(), from.cend(), [&to](const auto &updateItem)
{
to.append(updateItem.value);
});
};
}
QVector<QVariant> Grammar::prepareBindingsForUpdate(const BindingsMap &bindings,
const QVector<UpdateItem> &values) const
{
QVector<QVariant> preparedBindings;
// Merge join bindings from bindings map
mergeVector(preparedBindings, bindings.find(BindingType::JOIN).value());
// Merge update values bindings
mergeValuesForUpdate(preparedBindings, values);
// Flatten bindings map and exclude select and join bindings and than merge
// all remaining bindings from flatten bindings map.
mergeVector(preparedBindings,
flatBindingsForUpdate(const_cast<BindingsMap &>(bindings)));
return preparedBindings;
}
QString Grammar::compileDelete(const QueryBuilder &query) const
{
const auto table = query.getTable();
const auto wheres = compileWheres(query);
return query.getJoins().isEmpty() ? compileDeleteWithoutJoins(table, wheres)
: compileDeleteWithJoins(query, table, wheres);
}
namespace
{
/*! Flat bindings map and exclude select bindings. */
const auto flatBindingsForDelete = [](BindingsMap &bindings)
{
QVector<BindingType> bindingsToReject {BindingType::SELECT};
QVector<std::reference_wrapper<const QVariant>> cleanBindingsFlatten;
for (auto itBindingVector = bindings.constBegin(); itBindingVector != bindings.constEnd();
++itBindingVector)
{
if (bindingsToReject.contains(itBindingVector.key()))
continue;
for (const auto &binding : itBindingVector.value())
cleanBindingsFlatten.append(std::cref(binding));
}
return cleanBindingsFlatten;
};
}
QVector<QVariant> Grammar::prepareBindingsForDelete(const BindingsMap &bindings) const
{
QVector<QVariant> preparedBindings;
// Flatten bindings map and exclude select bindings and than merge
// all remaining bindings from flatten bindings map.
mergeVector(preparedBindings,
flatBindingsForDelete(const_cast<BindingsMap &>(bindings)));
return preparedBindings;
}
QString Grammar::compileTruncate(const QueryBuilder &query) const
{
return QStringLiteral("truncate table %1").arg(query.getFrom());
}
Grammar::ComponentsVector
Grammar::compileComponents(const QueryBuilder &query) const
{
ComponentsVector sql;
const auto &compileMap = getCompileMap();
for (const auto &component : compileMap)
if (component.isset)
if (component.isset(query))
sql.append(component.compileMethod(query));
return sql;
}
QString Grammar::compileColumns(const QueryBuilder &query) const
{
QString select;
if (query.getDistinct())
select += "select distinct ";
else
select += "select ";
return select + columnize(query.getColumns(), query.getFrom() == "torrents");
}
QString Grammar::compileFrom(const QueryBuilder &query) const
{
return QStringLiteral("from %1").arg(query.getFrom());
}
QString Grammar::compileJoins(const QueryBuilder &query) const
{
QVector<QString> sql;
for (const auto &join : query.getJoins()) {
sql << QStringLiteral("%1 join %2 %3").arg(
join->getType(),
join->getTable(),
compileWheres(*join));
}
return joinContainer(sql, QStringLiteral(" "));
}
QString Grammar::compileWheres(const QueryBuilder &query) const
{
const auto sql = compileWheresToVector(query);
if (sql.size() > 0)
return concatenateWhereClauses(query, sql);
return {};
}
QVector<QString> Grammar::compileWheresToVector(const QueryBuilder &query) const
{
QVector<QString> compiledWheres;
for (const auto &where : query.getWheres())
compiledWheres << QStringLiteral("%1 %2")
.arg(where.condition,
std::invoke(getWhereMethod(where.type), where));
return compiledWheres;
}
QString Grammar::concatenateWhereClauses(const QueryBuilder &query, const QVector<QString> &sql) const
{
// Is query instance of JoinClause?
const auto conjunction = dynamic_cast<const JoinClause *>(&query) == nullptr
? QStringLiteral("where")
: QStringLiteral("on");
return QStringLiteral("%1 %2").arg(
conjunction,
removeLeadingBoolean(joinContainer(sql, QStringLiteral(" "))));
}
QString Grammar::compileGroups(const QueryBuilder &query) const
{
return QStringLiteral("group by %1").arg(columnize(query.getGroups()));
}
QString Grammar::compileHavings(const QueryBuilder &query) const
{
QVector<QString> compiledHavings;
for (const auto &having : query.getHavings())
compiledHavings << compileHaving(having);
return QStringLiteral("having %1").arg(
removeLeadingBoolean(joinContainer(compiledHavings, QStringLiteral(" "))));
}
QString Grammar::compileHaving(const HavingConditionItem &having) const
{
switch (having.type) {
case HavingType::BASIC:
return QStringLiteral("%1 %2 %3 ?").arg(having.condition, having.column,
having.comparison);
default:
return {};
}
}
const QMap<Grammar::ComponentType, Grammar::ComponentValue> &
Grammar::getCompileMap() const
{
using std::placeholders::_1;
// Needed, because some compileXx() methods are overloaded
const auto getBind = [this](const auto &&func)
{
return std::bind(std::forward<decltype (func)>(func), this, _1);
};
// Pointers to a where member methods by whereType, yes yes c++ 😂
static const QMap<ComponentType, ComponentValue> cached {
// {ComponentType::AGGREGATE, {}},
{ComponentType::COLUMNS, {getBind(&Grammar::compileColumns),
[](const auto &query) { return !query.getColumns().isEmpty(); }}},
{ComponentType::FROM, {getBind(&Grammar::compileFrom),
[](const auto &query) { return !query.getFrom().isEmpty(); }}},
{ComponentType::JOINS, {getBind(&Grammar::compileJoins),
[](const auto &query) { return !query.getJoins().isEmpty(); }}},
{ComponentType::WHERES, {getBind(&Grammar::compileWheres),
[](const auto &query) { return !query.getWheres().isEmpty(); }}},
{ComponentType::GROUPS, {getBind(&Grammar::compileGroups),
[](const auto &query) { return !query.getGroups().isEmpty(); }}},
{ComponentType::HAVINGS, {getBind(&Grammar::compileHavings),
[](const auto &query) { return !query.getHavings().isEmpty(); }}},
{ComponentType::ORDERS, {getBind(&Grammar::compileOrders),
[](const auto &query) { return !query.getOrders().isEmpty(); }}},
{ComponentType::LIMIT, {getBind(&Grammar::compileLimit),
[](const auto &query) { return query.getLimit() > -1; }}},
{ComponentType::OFFSET, {getBind(&Grammar::compileOffset),
[](const auto &query) { return query.getOffset() > -1; }}},
// {ComponentType::LOCK, {}},
};
// TODO correct way to return const & for cached (static) local variable for QHash/QMap, check all 👿🤔 silverqx
return cached;
}
const std::function<QString(const WhereConditionItem &)> &
Grammar::getWhereMethod(const WhereType whereType) const
{
using std::placeholders::_1;
const auto getBind = [this](const auto &&func)
{
return std::bind(std::forward<decltype (func)>(func), this, _1);
};
// Pointers to a where member methods by whereType, yes yes c++ 😂
// An order has to be the same as in enum struct WhereType
static const QVector<std::function<QString(const WhereConditionItem &)>> cached {
getBind(&Grammar::whereBasic),
getBind(&Grammar::whereNested),
getBind(&Grammar::whereColumn),
getBind(&Grammar::whereIn),
getBind(&Grammar::whereNotIn),
getBind(&Grammar::whereNull),
getBind(&Grammar::whereNotNull),
};
static const auto size = cached.size();
// Check if whereType is in range, just for sure 😏
const auto type = static_cast<int>(whereType);
Q_ASSERT((0 <= type) && (type < size));
return cached.at(type);
}
QString Grammar::whereBasic(const WhereConditionItem &where) const
{
return QStringLiteral("%1 %2 ?").arg(where.column,
where.comparison);
}
QString Grammar::whereNested(const WhereConditionItem &where) const
{
/* Here we will calculate what portion of the string we need to remove. If this
is a join clause query, we need to remove the "on" portion of the SQL and
if it is a normal query we need to take the leading "where" of queries. */
const auto offset = 6;
return QStringLiteral("(%1)").arg(compileWheres(*where.nestedQuery)
.mid(offset));
}
QString Grammar::whereColumn(const WhereConditionItem &where) const
{
/* In this where type where.column contains first column and where,value contains
second column. */
return QStringLiteral("%1 %2 %3").arg(where.column,
where.comparison,
where.value.toString());
}
QString Grammar::whereIn(const WhereConditionItem &where) const
{
if (where.values.isEmpty())
return QStringLiteral("0 = 1");
return QStringLiteral("%1 in (%2)").arg(where.column,
parametrize(where.values));
}
QString Grammar::whereNotIn(const WhereConditionItem &where) const
{
if (where.values.isEmpty())
return QStringLiteral("1 = 1");
return QStringLiteral("%1 not in (%2)").arg(where.column,
parametrize(where.values));
}
QString Grammar::whereNull(const WhereConditionItem &where) const
{
return QStringLiteral("%1 is null").arg(where.column);
}
QString Grammar::whereNotNull(const WhereConditionItem &where) const
{
return QStringLiteral("%1 is not null").arg(where.column);
}
QString Grammar::compileOrders(const QueryBuilder &query) const
{
return QStringLiteral("order by %1").arg(
joinContainer(compileOrdersToVector(query), QStringLiteral(", ")));
}
QVector<QString> Grammar::compileOrdersToVector(const QueryBuilder &query) const
{
QVector<QString> compiledOrders;
for (const auto &order : query.getOrders())
compiledOrders << QStringLiteral("%1 %2")
.arg(order.column, order.direction.toLower());
return compiledOrders;
}
QString Grammar::compileLimit(const QueryBuilder &query) const
{
return QStringLiteral("limit %1").arg(query.getLimit());
}
QString Grammar::compileOffset(const QueryBuilder &query) const
{
return QStringLiteral("offset %1").arg(query.getOffset());
}
QVector<QString> Grammar::compileInsertToVector(const QVector<QVariantMap> &values) const
{
/* We need to build a list of parameter place-holders of values that are bound
to the query. Each insert should have the exact same amount of parameter
bindings so we will loop through the record and parameterize them all. */
QVector<QString> compiledParameters;
for (const auto &valuesMap : values)
compiledParameters << QStringLiteral("(%1)").arg(parametrize(valuesMap));
return compiledParameters;
}
QString Grammar::columnize(const QStringList &columns) const
{
return joinContainer(columns, QStringLiteral(", "));
}
QString Grammar::columnize(const QStringList &columns, const bool isTorrentsTable) const
{
/* Qt don't know how to iterate the result with json column, so I have to manually manage
columns in the select clause. */
if (isTorrentsTable && (columns.size() == 1) && (columns.at(0) == "*")) {
// static const QString cached {
// "id, name, progress, eta, size, seeds, total_seeds, leechers, "
// "total_leechers, remaining, added_on, hash, status, "
// "movie_detail_index, savepath"
// };
static const QString cached {
"id, name, size, progress, added_on, hash"
};
return cached;
}
return columnize(columns);
}
template<typename Container>
QString Grammar::parametrize(const Container &values) const
{
QVector<QString> compiledParameters;
for (const auto &value : values)
compiledParameters << parameter(value);
// TODO move all common QStringLiteral() to the common file silverqx
return joinContainer(compiledParameters, QStringLiteral(", "));
}
QString Grammar::parameter(const QVariant &value) const
{
// TODO rethink expressions, how to work with them and pass them to the query builder 🤔 silverqx
return value.canConvert<Expression>()
? value.value<Expression>().getValue().toString()
: QStringLiteral("?");
}
QString Grammar::removeLeadingBoolean(QString statement) const
{
return statement.replace(
QRegularExpression(QStringLiteral("^(and |or )"),
QRegularExpression::CaseInsensitiveOption),
QStringLiteral(""));
}
QString Grammar::compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values,
const bool ignore) const
{
Q_ASSERT(values.size() > 0);
return QStringLiteral("insert%1 into %2 (%3) values %4").arg(
ignore ? QStringLiteral(" ignore") : QStringLiteral(""),
query.getTable(),
// Columns are obtained only from a first QMap
columnize(values.at(0).keys()),
joinContainer(compileInsertToVector(values),
QStringLiteral(", ")));
}
QString Grammar::compileUpdateColumns(const QVector<UpdateItem> &values) const
{
QVector<QString> compiledAssignments;
for (const auto &assignment : values)
compiledAssignments << QStringLiteral("%1 = %2").arg(
assignment.column,
parameter(assignment.value));
return joinContainer(compiledAssignments, QStringLiteral(", "));
}
QString Grammar::compileUpdateWithoutJoins(const QString &table, const QString &columns,
const QString &wheres) const
{
return QStringLiteral("update %1 set %2 %3").arg(table, columns, wheres);
}
QString Grammar::compileUpdateWithJoins(const QueryBuilder &query, const QString &table,
const QString &columns, const QString &wheres) const
{
const auto joins = compileJoins(query);
return QStringLiteral("update %1 %2 set %3 %4").arg(table, joins, columns, wheres);
}
QString Grammar::compileDeleteWithoutJoins(const QString &table, const QString &wheres) const
{
return QStringLiteral("delete from %1 %2").arg(table, wheres);
}
QString Grammar::compileDeleteWithJoins(const QueryBuilder &query, const QString &table,
const QString &wheres) const
{
/* Alias has to be after the delete keyword and aliased table definition after the
from keyword. */
const auto alias = table.splitRef(QStringLiteral(" as ")).last().trimmed();
const auto joins = compileJoins(query);
return QStringLiteral("delete %1 from %2 %3 %4").arg(alias, table, joins, wheres);
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

140
src/orm/grammar.h Normal file
View File

@@ -0,0 +1,140 @@
#ifndef GRAMMAR_H
#define GRAMMAR_H
#include "orm/ormtypes.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class Grammar
{
public:
/*! Compile a select query into SQL. */
QString compileSelect(const QueryBuilder &query) const;
/*! Compile an insert statement into SQL. */
QString compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values) const;
/*! Compile an insert ignore statement into SQL. */
QString compileInsertOrIgnore(const QueryBuilder &query,
const QVector<QVariantMap> &values) const;
/*! Compile an update statement into SQL. */
QString compileUpdate(const QueryBuilder &query, const QVector<UpdateItem> &values) const;
/*! Prepare the bindings for an update statement. */
QVector<QVariant> prepareBindingsForUpdate(const BindingsMap &bindings,
const QVector<UpdateItem> &values) const;
/*! Compile a delete statement into SQL. */
QString compileDelete(const QueryBuilder &query) const;
/*! Prepare the bindings for a delete statement. */
QVector<QVariant> prepareBindingsForDelete(const BindingsMap &bindings) const;
/*! Compile a truncate table statement into SQL. */
QString compileTruncate(const QueryBuilder &query) const;
protected:
// TODO methods below should be abstracted to DatabaseGrammar silverqx
/*! Convert an array of column names into a delimited string. */
QString columnize(const QStringList &columns) const;
QString columnize(const QStringList &columns, bool isTorrentsTable) const;
// TODO add template constraint to QVariantMap and QVector<QVariant> for now silverqx
/*! Create query parameter place-holders for an array. */
template<typename Container>
QString parametrize(const Container &values) const;
/*! Get the appropriate query parameter place-holder for a value. */
QString parameter(const QVariant &value) const;
/*! Remove the leading boolean from a statement. */
QString removeLeadingBoolean(QString statement) const;
private:
using ComponentsVector = QVector<QString>;
enum struct ComponentType
{
AGGREGATE,
COLUMNS,
FROM,
JOINS,
WHERES,
GROUPS,
HAVINGS,
ORDERS,
LIMIT,
OFFSET,
LOCK,
};
struct ComponentValue
{
std::function<QString(const QueryBuilder &)> compileMethod;
std::function<bool(const QueryBuilder &)> isset;
};
/*! Compile the components necessary for a select clause. */
ComponentsVector compileComponents(const QueryBuilder &query) const;
/*! Compile the "select *" portion of the query. */
QString compileColumns(const QueryBuilder &query) const;
QString compileFrom(const QueryBuilder &query) const;
/*! Compile the "join" portions of the query. */
QString compileJoins(const QueryBuilder &query) const;
QString compileWheres(const QueryBuilder &query) const;
QVector<QString> compileWheresToVector(const QueryBuilder &query) const;
QString concatenateWhereClauses(const QueryBuilder &query, const QVector<QString> &sql) const;
QString compileGroups(const QueryBuilder &query) const;
QString compileHavings(const QueryBuilder &query) const;
QString compileHaving(const HavingConditionItem &having) const;
/*! Map the ComponentType to a Grammar::compileXx() methods. */
const QMap<ComponentType, ComponentValue> &
getCompileMap() const;
/*! Map the WhereType to a Grammar::whereXx() methods. */
const std::function<QString(const WhereConditionItem &)> &
getWhereMethod(WhereType whereType) const;
/*! Compile a basic where clause. */
QString whereBasic(const WhereConditionItem &where) const;
/*! Compile a nested where clause. */
QString whereNested(const WhereConditionItem &where) const;
/*! Compile a where clause comparing two columns. */
QString whereColumn(const WhereConditionItem &where) const;
/*! Compile a "where in" clause. */
QString whereIn(const WhereConditionItem &where) const;
/*! Compile a "where not in" clause. */
QString whereNotIn(const WhereConditionItem &where) const;
/*! Compile a "where null" clause. */
QString whereNull(const WhereConditionItem &where) const;
/*! Compile a "where not null" clause. */
QString whereNotNull(const WhereConditionItem &where) const;
QString compileOrders(const QueryBuilder &query) const;
QVector<QString> compileOrdersToVector(const QueryBuilder &query) const;
QString compileLimit(const QueryBuilder &query) const;
QString compileOffset(const QueryBuilder &query) const;
/*! Compile a insert values lists. */
QVector<QString> compileInsertToVector(const QVector<QVariantMap> &values) const;
QString compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values,
bool ignore) const;
/*! Compile the columns for an update statement. */
QString compileUpdateColumns(const QVector<UpdateItem> &values) const;
/*! Compile an update statement without joins into SQL. */
QString compileUpdateWithoutJoins(const QString &table, const QString &columns,
const QString &wheres) const;
/*! Compile an update statement with joins into SQL. */
QString compileUpdateWithJoins(const QueryBuilder &query, const QString &table,
const QString &columns, const QString &wheres) const;
/*! Compile a delete statement without joins into SQL. */
QString compileDeleteWithoutJoins(const QString &table, const QString &wheres) const;
/*! Compile a delete statement with joins into SQL. */
QString compileDeleteWithJoins(const QueryBuilder &query, const QString &table,
const QString &wheres) const;
};
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // GRAMMAR_H

35
src/orm/logquery.cpp Normal file
View File

@@ -0,0 +1,35 @@
#include "logquery.h"
#include <QDebug>
#include <QtSql/QSqlQuery>
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
QString parseExecutedQuery(const QSqlQuery &query)
{
auto executedQuery = query.executedQuery();
const auto boundValues = query.boundValues().values();
for (int i = 0; i < boundValues.size(); ++i) {
const auto boundValueRaw = boundValues.at(i);
// Support for string quoting
const auto boundValue = (boundValueRaw.type() == QVariant::Type::String)
? QStringLiteral("\"%1\"").arg(boundValueRaw.toString())
: boundValueRaw.toString();
executedQuery.replace(executedQuery.indexOf('?'), 1, boundValue);
}
return executedQuery;
}
void logExecutedQuery(const QSqlQuery &query)
{
qDebug().noquote() << QStringLiteral("Executed Query :")
<< parseExecutedQuery(query);
}
#ifdef MANGO_COMMON_NAMESPACE
}
#endif

34
src/orm/logquery.h Normal file
View File

@@ -0,0 +1,34 @@
#ifndef LOGQUERY_H
#define LOGQUERY_H
class QSqlQuery;
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
// TODO code which was here, will be reverted after merge of the logQuery() and logExecutedQuery() silverqx
QString parseExecutedQuery(const QSqlQuery &query);
#ifdef QT_DEBUG
# ifndef Q_CC_MSVC
Q_NORETURN
# endif
Q_DECL_UNUSED
void logExecutedQuery(const QSqlQuery &query);
#endif
#ifndef LOG_EXECUTED_QUERY
# ifdef QT_DEBUG
# define LOG_EXECUTED_QUERY(query) logExecutedQuery(query)
# else
# define LOG_EXECUTED_QUERY(query) qt_noop()
# endif
#endif
#ifdef MANGO_COMMON_NAMESPACE
}
#endif
#endif // LOGQUERY_H

28
src/orm/ormerror.h Normal file
View File

@@ -0,0 +1,28 @@
#ifndef ORMERROR_H
#define ORMERROR_H
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
// TODO investigate and rework all orm exception classes silverqx
class OrmError final : public std::runtime_error
{
public:
explicit inline OrmError(const char *Message)
: std::runtime_error(Message)
{}
explicit inline OrmError(const QString &Message)
: std::runtime_error(Message.toUtf8().constData())
{}
};
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // ORMERROR_H

27
src/orm/ormtypes.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "ormtypes.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
AssignmentList::AssignmentList(const QVariantHash &variantHash)
{
auto itHash = variantHash.constBegin();
while (itHash != variantHash.constEnd()) {
*this << AssignmentListItem({itHash.key(), itHash.value()});
++itHash;
}
}
//bool operator==(const WithItem &lhs, const WithItem &rhs)
//{
// return (lhs.name == rhs.name);
//}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

160
src/orm/ormtypes.h Normal file
View File

@@ -0,0 +1,160 @@
#ifndef ORMTYPES_H
#define ORMTYPES_H
#include <QDebug>
#include <QSharedPointer>
#include <memory>
#include <any>
#include <typeindex>
#include "orm/tiny/relations/relation.h"
class Torrent;
class TorrentPreviewableFile;
class TorrentPeer;
// TODO divide OrmTypes to internal and types which user will / may need, so divide to two files silverqx
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
enum struct BindingType
{
SELECT,
FROM,
JOIN,
WHERE,
GROUPBY,
HAVING,
ORDER,
UNION,
UNIONORDER,
};
// TODO have to be QMap<BindingType, QVector<BindingValue>> to correctly support Expressions silverqx
using BindingsMap = QMap<BindingType, QVector<QVariant>>;
enum struct WhereType
{
UNDEFINED = -1,
BASIC,
NESTED,
COLUMN,
IN_,
NOT_IN,
NULL_,
NOT_NULL,
};
struct WhereItem
{
QString column;
QVariant value;
QString comparison {"="};
};
namespace Query
{
class Builder;
}
using QueryBuilder = Query::Builder;
struct WhereConditionItem
{
QString column;
QVariant value {};
QString comparison {"="};
QString condition {"and"};
WhereType type {WhereType::UNDEFINED};
QSharedPointer<QueryBuilder> nestedQuery {nullptr};
QVector<QVariant> values {};
};
enum struct HavingType
{
UNDEFINED = -1,
BASIC,
};
struct HavingConditionItem
{
QString column;
QVariant value;
QString comparison {"="};
QString condition {"and"};
HavingType type {HavingType::UNDEFINED};
};
struct AssignmentListItem
{
QString column;
QVariant value;
};
class AssignmentList final : public QVector<AssignmentListItem>
{
public:
// Inherit all base class constructors, wow 😲✨
using QVector<AssignmentListItem>::QVector;
AssignmentList(const QVariantHash &variantHash);
};
struct OrderByItem
{
QString column;
QString direction {"asc"};
};
struct UpdateItem
{
QString column;
QVariant value;
};
struct ResultItem
{
QString column;
QVariant value;
};
struct AttributeItem
{
QString key;
QVariant value;
};
/*! Eager load relation item. */
struct WithItem
{
QString name;
std::function<void()> constraints {nullptr};
};
// bool operator==(const WithItem &lhs, const WithItem &rhs);
/*! Tag for BaseModel::getRelation() family methods to return Related type directly ( not container type ). */
struct One {};
// TODO Many internal type only for now silverqx
/*! Tag for BaseModel::getRelationshipFromMethod() to return QVector<Related> type ( 'Many' relation types ). */
struct Many {};
/*! The type returned by Model's relation methods. */
template<typename Model, typename Related>
using RelationType = std::unique_ptr<Tiny::Relations::Relation<Model, Related>>(Model::*)();
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#ifdef MANGO_COMMON_NAMESPACE
Q_DECLARE_METATYPE(MANGO_COMMON_NAMESPACE::Orm::WhereConditionItem);
#else
Q_DECLARE_METATYPE(Orm::WhereConditionItem);
#endif
#endif // ORMTYPES_H

View File

@@ -0,0 +1,26 @@
#include "joinclause.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Query
{
JoinClause::JoinClause(const Builder &query, const QString &type, const QString &table)
: Builder(query.getConnection(), query.getGrammar())
, m_type(type)
, m_table(table)
{}
JoinClause &JoinClause::on(const QString &first, const QString &comparison,
const QString &second, const QString &condition)
{
whereColumn(first, comparison, second, condition);
return *this;
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,43 @@
#ifndef JOINCLAUSE_H
#define JOINCLAUSE_H
#include "orm/query/querybuilder.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class DatabaseConnection;
class Grammar;
}
namespace Orm::Query
{
class JoinClause final : public Builder
{
public:
JoinClause(const Builder &query, const QString &type, const QString &table);
JoinClause &on(const QString &first, const QString &comparison,
const QString &second, const QString &condition = "and");
const QString &getType() const
{ return m_type; }
const QString &getTable() const
{ return m_table; }
private:
/*! The type of join being performed. */
const QString m_type;
/*! The table the join clause is joining to. */
const QString m_table;
};
} // namespace Orm::Query
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // JOINCLAUSE_H

View File

@@ -0,0 +1,581 @@
#include "querybuilder.h"
#include "orm/databaseconnection.h"
#include "orm/ormerror.h"
#include "orm/query/joinclause.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Query
{
Builder::Builder(const DatabaseConnection &db, const Grammar &grammar)
: m_db(db)
, m_grammar(grammar)
{}
std::tuple<bool, QSqlQuery>
Builder::get(const QStringList &columns)
{
if ((false == ((columns.size() == 1) && (columns.at(0) == "*")))
&& !columns.empty())
m_columns = columns;
return runSelect();
}
std::tuple<bool, QSqlQuery> Builder::first(const QStringList &columns)
{
auto [ok, query] = take(1).get(columns);
if (!ok || !query.isSelect() || !query.isActive())
return {false, query};
return {query.first(), query};
}
QVariant Builder::value(const QString &column)
{
auto [firstOk, query] = first({column});
if (!firstOk)
return {};
return query.value(column);
}
std::tuple<bool, QSqlQuery>
Builder::find(const QVariant &id, const QStringList &columns)
{
return where("id", "=", id).first(columns);
}
QString Builder::toSql() const
{
return m_grammar.compileSelect(*this);
}
std::tuple<bool, std::optional<QSqlQuery>>
Builder::insert(const QVariantMap &values) const
{
return insert(QVector<QVariantMap> {values});
}
namespace
{
const auto flatValuesForInsert = [](const auto &values)
{
QVector<QVariant> flattenValues;
for (const auto &insertMap : values)
for (const auto &value : insertMap)
flattenValues << value;
return flattenValues;
};
}
std::tuple<bool, std::optional<QSqlQuery>>
Builder::insert(const QVector<QVariantMap> &values) const
{
if (values.isEmpty())
return {true, std::nullopt};
return m_db.insert(m_grammar.compileInsert(*this, values),
flatValuesForInsert(values));
}
std::tuple<int, std::optional<QSqlQuery>>
Builder::insertOrIgnore(const QVector<QVariantMap> &values) const
{
if (values.isEmpty())
return {0, std::nullopt};
return m_db.affectingStatement(m_grammar.compileInsertOrIgnore(*this, values),
flatValuesForInsert(values));
}
std::tuple<int, std::optional<QSqlQuery>>
Builder::insertOrIgnore(const QVariantMap &values) const
{
return insertOrIgnore(QVector<QVariantMap> {values});
}
quint64 Builder::insertGetId(const QVariantMap &values) const
{
auto [ok, query] = insert({values});
if (!ok)
return 0;
return query->lastInsertId().toULongLong();
}
std::tuple<int, QSqlQuery>
Builder::update(const QVector<UpdateItem> &values) const
{
return m_db.update(m_grammar.compileUpdate(*this, values),
cleanBindings(m_grammar.prepareBindingsForUpdate(getRawBindings(),
values)));
}
std::tuple<int, QSqlQuery> Builder::deleteRow() const
{
return remove();
}
std::tuple<int, QSqlQuery> Builder::remove() const
{
return m_db.remove(m_grammar.compileDelete(*this),
cleanBindings(m_grammar.prepareBindingsForDelete(getRawBindings())));
}
std::tuple<int, QSqlQuery> Builder::deleteRow(const quint64 id)
{
return remove(id);
}
std::tuple<int, QSqlQuery> Builder::remove(const quint64 id)
{
/* If an ID is passed to the method, we will set the where clause to check the
ID to let developers to simply and quickly remove a single row from this
database without manually specifying the "where" clauses on the query. */
where(m_from + ".id", "=", id, "and");
return remove();
}
std::tuple<bool, QSqlQuery> Builder::truncate() const
{
return m_db.statement(m_grammar.compileTruncate(*this));
}
Builder &Builder::where(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition)
{
// Compile check for a invalid comparison operator
invalidOperator(comparison);
m_wheres.append({column, value, comparison, condition, WhereType::BASIC});
addBinding(value, BindingType::WHERE);
return *this;
}
Builder &Builder::orWhere(const QString &column, const QString &comparison,
const QVariant &value)
{
return where(column, comparison, value, "or");
}
Builder &Builder::where(const std::function<void(Builder &)> &callback,
const QString &condition)
{
const auto query = forNestedWhere();
std::invoke(callback, *query);
return addNestedWhereQuery(query, condition);
}
Builder &Builder::orWhere(const std::function<void (Builder &)> &callback)
{
return where(callback, "or");
}
Builder &Builder::whereEq(const QString &column, const QVariant &value,
const QString &condition)
{
return where(column, "=", value, condition);
}
Builder &Builder::orWhereEq(const QString &column, const QVariant &value)
{
return where(column, "=", value, "or");
}
Builder &Builder::whereColumn(const QString &first, const QString &comparison,
const QString &second, const QString &condition)
{
// Compile check for a invalid comparison operator
invalidOperator(comparison);
m_wheres.append({first, second, comparison, condition, WhereType::COLUMN});
return *this;
}
Builder &Builder::orWhereColumn(const QString &first, const QString &comparison,
const QString &second)
{
return whereColumn(first, comparison, second, "or");
}
Builder &Builder::whereIn(const QString &column, const QVector<QVariant> &values,
const QString &condition, const bool nope)
{
const auto type = nope ? WhereType::NOT_IN : WhereType::IN_;
m_wheres.append({.column = column, .condition = condition, .type = type, .values = values});
/* Finally we'll add a binding for each values unless that value is an expression
in which case we will just skip over it since it will be the query as a raw
string and not as a parameterized place-holder to be replaced by the DB driver. */
addBinding(cleanBindings(values), BindingType::WHERE);
return *this;
}
Builder &Builder::orWhereIn(const QString &column, const QVector<QVariant> &values)
{
return whereIn(column, values, "or");
}
Builder &Builder::whereNotIn(const QString &column, const QVector<QVariant> &values,
const QString &condition)
{
return whereIn(column, values, condition, true);
}
Builder &Builder::orWhereNotIn(const QString &column, const QVector<QVariant> &values)
{
return whereNotIn(column, values, "or");
}
Builder &Builder::whereNull(const QStringList &columns, const QString &condition,
const bool nope)
{
const auto type = nope ? WhereType::NOT_NULL : WhereType::NULL_;
for (const auto &column : columns)
m_wheres.append({.column = column, .condition = condition, .type = type});
return *this;
}
Builder &Builder::whereNull(const QString &column, const QString &condition,
const bool nope)
{
return whereNull(QStringList(column), condition, nope);
}
Builder &Builder::orWhereNull(const QStringList &columns)
{
return whereNull(columns, QStringLiteral("or"));
}
Builder &Builder::orWhereNull(const QString &column)
{
return orWhereNull(QStringList(column));
}
Builder &Builder::whereNotNull(const QStringList &columns, const QString &condition)
{
return whereNull(columns, condition, true);
}
Builder &Builder::whereNotNull(const QString &column, const QString &condition)
{
return whereNotNull(QStringList(column), condition);
}
Builder &Builder::orWhereNotNull(const QStringList &columns)
{
return whereNotNull(columns, QStringLiteral("or"));
}
Builder &Builder::orWhereNotNull(const QString &column)
{
return orWhereNotNull(QStringList(column));
}
Builder &Builder::groupBy(const QStringList &groups)
{
if (groups.isEmpty())
return *this;
std::copy(groups.cbegin(), groups.cend(), std::back_inserter(m_groups));
return *this;
}
Builder &Builder::having(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition)
{
// Compile check for a invalid comparison operator
invalidOperator(comparison);
m_havings.append({column, value, comparison, condition, HavingType::BASIC});
addBinding(value, BindingType::HAVING);
return *this;
}
Builder &Builder::orHaving(const QString &column, const QString &comparison,
const QVariant &value)
{
return having(column, comparison, value, "or");
}
Builder &Builder::join(const QString &table, const QString &first,
const QString &comparison, const QString &second,
const QString &type, const bool where)
{
const auto join = newJoinClause(*this, type, table);
enum struct JoinType { WHERE, ON };
const auto method = where ? JoinType::WHERE : JoinType::ON;
switch (method) {
case JoinType::WHERE:
join->where(first, comparison, second);
break;
case JoinType::ON:
join->on(first, comparison, second);
break;
}
m_joins.append(join);
addBinding(join->getBindings(), BindingType::JOIN);
return *this;
}
Builder &Builder::join(const QString &table, const std::function<void(JoinClause &)> &callback,
const QString &type)
{
const auto join = newJoinClause(*this, type, table);
std::invoke(callback, *join);
m_joins.append(join);
addBinding(join->getBindings(), BindingType::JOIN);
return *this;
}
Builder &Builder::leftJoin(const QString &table,
const std::function<void (JoinClause &)> &callback)
{
return join(table, callback, "left");
}
Builder &Builder::rightJoin(const QString &table,
const std::function<void (JoinClause &)> &callback)
{
return join(table, callback, "right");
}
Builder &Builder::crossJoin(const QString &table,
const std::function<void (JoinClause &)> &callback)
{
return join(table, callback, "cross");
}
Builder &Builder::joinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second,
const QString &type)
{
return join(table, first, comparison, second, type, true);
}
Builder &Builder::leftJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second)
{
return join(table, first, comparison, second, "left");
}
Builder &Builder::leftJoinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second)
{
return join(table, first, comparison, second, "left");
}
Builder &Builder::rightJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second)
{
return join(table, first, comparison, second, "right");
}
Builder &Builder::rightJoinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second)
{
return joinWhere(table, first, comparison, second, "right");
}
Builder &Builder::crossJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second)
{
return join(table, first, comparison, second, "cross");
}
Builder &Builder::orderBy(const QString &column, const QString &direction)
{
const auto &directionLower = direction.toLower();
if ((directionLower != "asc") && (directionLower != "desc"))
throw OrmError("Order direction must be \"asc\" or \"desc\", case is not important.");
m_orders.append({column, directionLower});
return *this;
}
Builder &Builder::orderByDesc(const QString &column)
{
return orderBy(column, "desc");
}
Builder &Builder::latest(const QString &column)
{
return orderBy(column, "desc");
}
Builder &Builder::oldest(const QString &column)
{
return orderBy(column, "asc");
}
Builder &Builder::reorder()
{
m_orders.clear();
m_bindings.find(BindingType::ORDER)->clear();
return *this;
}
Builder &Builder::reorder(const QString &column, const QString &direction)
{
reorder();
return orderBy(column, direction);
}
Builder &Builder::limit(const int value)
{
Q_ASSERT(value >= 0);
m_limit = value;
return *this;
}
Builder &Builder::take(const int value)
{
return limit(value);
}
Builder &Builder::offset(const int value)
{
Q_ASSERT(value >= 0);
m_offset = std::max(0, value);
return *this;
}
Builder &Builder::skip(const int value)
{
return offset(value);
}
Builder &Builder::forPage(const int page, const int perPage)
{
return offset((page - 1) * perPage).limit(perPage);
}
QVector<QVariant> Builder::getBindings() const
{
QVector<QVariant> flattenBindings;
std::for_each(m_bindings.cbegin(), m_bindings.cend(),
[&flattenBindings](const auto &bindings)
{
for (const auto &binding : bindings)
flattenBindings.append(binding);
});
return flattenBindings;
}
QSharedPointer<Builder> Builder::newQuery() const
{
return QSharedPointer<Builder>::create(m_db, m_grammar);
}
QSharedPointer<Builder> Builder::forNestedWhere() const
{
const auto query = newQuery();
query->from(m_from);
return query;
}
Expression Builder::raw(const QVariant &value) const
{
return m_db.raw(value);
}
bool Builder::invalidOperator(const QString &comparison) const
{
const auto contains = m_operators.contains(comparison);
Q_ASSERT(contains);
return contains == false;
}
Builder &Builder::addBinding(const QVariant &binding, const BindingType type)
{
if (!m_bindings.contains(type))
// TODO add hash to map BindingType to QString silverqx
throw OrmError(QStringLiteral("Invalid binding type: %1").arg(static_cast<int>(type)));
m_bindings[type].append(binding);
return *this;
}
Builder &Builder::addBinding(const QVector<QVariant> &bindings, const BindingType type)
{
// TODO duplicate check, unify silverqx
if (!m_bindings.contains(type))
// TODO add hash to map BindingType to QString silverqx
throw OrmError(QStringLiteral("Invalid binding type: %1").arg(static_cast<int>(type)));
std::copy(bindings.cbegin(), bindings.cend(), std::back_inserter(m_bindings[type]));
return *this;
}
// TODO investigate extended lifetime of reference in cleanBindings(), important case 🤔 silverqx
QVector<QVariant> Builder::cleanBindings(const QVector<QVariant> &bindings) const
{
// TODO investigate const, move, reserve() vs ctor(size), nice example of move semantics 😏 silverqx
QVector<QVariant> cleanedBindings;
cleanedBindings.reserve(bindings.size());
for (auto &binding : bindings)
if (!binding.canConvert<Expression>())
cleanedBindings.append(binding);
return cleanedBindings;
}
QSharedPointer<JoinClause>
Builder::newJoinClause(const Builder &query, const QString &type, const QString &table) const
{
return QSharedPointer<JoinClause>::create(query, type, table);
}
Builder &Builder::addNestedWhereQuery(const QSharedPointer<Builder> query,
const QString &condition)
{
const auto &rawBindings = query->getRawBindings();
Q_ASSERT(rawBindings.contains(BindingType::WHERE));
const auto &whereBindings = rawBindings.find(BindingType::WHERE).value();
if (whereBindings.size() > 0) {
m_wheres.append({.condition = condition, .type = WhereType::NESTED, .nestedQuery = query});
addBinding(whereBindings, BindingType::WHERE);
}
return *this;
}
std::tuple<bool, QSqlQuery> Builder::runSelect() const
{
return m_db.select(toSql(), getBindings());
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,340 @@
#ifndef QUERYBUILDER_H
#define QUERYBUILDER_H
#include <QtSql/QSqlQuery>
#include <optional>
#include "orm/expression.h"
#include "orm/ormtypes.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class DatabaseConnection;
class Grammar;
}
namespace Orm::Query
{
class JoinClause;
class Builder
{
public:
Builder(const DatabaseConnection &db, const Grammar &grammar);
// WARNING solver pure virtual dtor vs default silverqx
virtual ~Builder() = default;
inline Builder &distinct()
{ m_distinct = true; return *this; };
inline Builder &from(const QString &table)
{ m_from = table; return *this; }
inline Builder &table(const QString &table)
{ m_from = table; return *this; }
/*! Execute the query as a "select" statement. */
std::tuple<bool, QSqlQuery>
get(const QStringList &columns = {"*"});
/*! Execute the query and get the first result. */
std::tuple<bool, QSqlQuery>
first(const QStringList &columns = {"*"});
/*! Get a single column's value from the first result of a query. */
QVariant value(const QString &column);
/*! Execute a query for a single record by ID. */
std::tuple<bool, QSqlQuery>
find(const QVariant &id, const QStringList &columns = {"*"});
/*! Get the SQL representation of the query. */
QString toSql() const;
/*! Insert new records into the database. */
std::tuple<bool, std::optional<QSqlQuery>>
insert(const QVariantMap &values) const;
/*! Insert new records into the database. */
std::tuple<bool, std::optional<QSqlQuery>>
insert(const QVector<QVariantMap> &values) const;
/*! Insert new records into the database while ignoring errors. */
std::tuple<int, std::optional<QSqlQuery>>
insertOrIgnore(const QVector<QVariantMap> &values) const;
std::tuple<int, std::optional<QSqlQuery>>
insertOrIgnore(const QVariantMap &values) const;
/*! Insert a new record and get the value of the primary key. */
quint64 insertGetId(const QVariantMap &values) const;
/*! Update records in the database. */
std::tuple<int, QSqlQuery>
update(const QVector<UpdateItem> &values) const;
/*! Delete records from the database. */
std::tuple<int, QSqlQuery>
deleteRow() const;
std::tuple<int, QSqlQuery>
remove() const;
std::tuple<int, QSqlQuery>
deleteRow(const quint64 id);
std::tuple<int, QSqlQuery>
remove(const quint64 id);
/*! Run a truncate statement on the table. */
std::tuple<bool, QSqlQuery> truncate() const;
/*! Add a join clause to the query. */
Builder &join(const QString &table, const QString &first,
const QString &comparison, const QString &second,
const QString &type = "inner", bool where = false);
Builder &join(const QString &table,
const std::function<void(JoinClause &)> &callback,
const QString &type = "inner");
Builder &leftJoin(const QString &table,
const std::function<void(JoinClause &)> &callback);
Builder &rightJoin(const QString &table,
const std::function<void(JoinClause &)> &callback);
Builder &crossJoin(const QString &table,
const std::function<void(JoinClause &)> &callback);
Builder &joinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second,
const QString &type = "inner");
Builder &leftJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second);
Builder &leftJoinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second);
Builder &rightJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second);
Builder &rightJoinWhere(const QString &table, const QString &first,
const QString &comparison, const QString &second);
Builder &crossJoin(const QString &table, const QString &first,
const QString &comparison, const QString &second);
/*! Add a basic where clause to the query. */
Builder &where(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition = "and");
Builder &orWhere(const QString &column, const QString &comparison,
const QVariant &value);
/*! Add a nested where clause to the query. */
Builder &where(const std::function<void(Builder &)> &callback,
const QString &condition = "and");
Builder &orWhere(const std::function<void(Builder &)> &callback);
Builder &whereEq(const QString &column, const QVariant &value,
const QString &condition = "and");
Builder &orWhereEq(const QString &column, const QVariant &value);
/*! Add a "where" clause comparing two columns to the query. */
Builder &whereColumn(const QString &first, const QString &comparison,
const QString &second, const QString &condition = "and");
Builder &orWhereColumn(const QString &first, const QString &comparison,
const QString &second);
/*! Add a "where in" clause to the query. */
Builder &whereIn(const QString &column, const QVector<QVariant> &values,
const QString &condition = "and", bool nope = false);
/*! Add an "or where in" clause to the query. */
Builder &orWhereIn(const QString &column, const QVector<QVariant> &values);
/*! Add a "where not in" clause to the query. */
Builder &whereNotIn(const QString &column, const QVector<QVariant> &values,
const QString &condition = "and");
/*! Add an "or where not in" clause to the query. */
Builder &orWhereNotIn(const QString &column, const QVector<QVariant> &values);
/*! Add a "where null" clause to the query. */
Builder &whereNull(const QStringList &columns = {"*"},
const QString &condition = "and", bool nope = false);
/*! Add a "where null" clause to the query. */
Builder &whereNull(const QString &column, const QString &condition = "and",
bool nope = false);
/*! Add an "or where null" clause to the query. */
Builder &orWhereNull(const QStringList &columns = {"*"});
/*! Add an "or where null" clause to the query. */
Builder &orWhereNull(const QString &column);
/*! Add a "where not null" clause to the query. */
Builder &whereNotNull(const QStringList &columns = {"*"},
const QString &condition = "and");
/*! Add a "where not null" clause to the query. */
Builder &whereNotNull(const QString &column, const QString &condition = "and");
/*! Add an "or where not null" clause to the query. */
Builder &orWhereNotNull(const QStringList &columns = {"*"});
/*! Add an "or where not null" clause to the query. */
Builder &orWhereNotNull(const QString &column);
/*! Add a "group by" clause to the query. */
Builder &groupBy(const QStringList &groups);
/*! Add a "having" clause to the query. */
Builder &having(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition = "and");
/*! Add an "or having" clause to the query. */
Builder &orHaving(const QString &column, const QString &comparison,
const QVariant &value);
/*! Add an "order by" clause to the query. */
Builder &orderBy(const QString &column, const QString &direction = "asc");
/*! Add a descending "order by" clause to the query. */
Builder &orderByDesc(const QString &column);
/*! Add an "order by" clause for a timestamp to the query. */
Builder &latest(const QString &column = "created_at");
/*! Add an "order by" clause for a timestamp to the query. */
Builder &oldest(const QString &column = "created_at");
/*! Remove all existing orders. */
Builder &reorder();
/*! Remove all existing orders and optionally add a new order. */
Builder &reorder(const QString &column, const QString &direction = "asc");
/*! Set the "limit" value of the query. */
Builder &limit(int value);
/*! Alias to set the "limit" value of the query. */
Builder &take(int value);
/*! Set the "offset" value of the query. */
Builder &offset(int value);
/*! Alias to set the "offset" value of the query. */
Builder &skip(int value);
/*! Set the limit and offset for a given page. */
Builder &forPage(int page, int perPage = 30);
/*! Increment a column's value by a given amount. */
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
increment(const QString &column, T amount = 1,
const QVector<UpdateItem> &extra = {}) const;
/*! Decrement a column's value by a given amount. */
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
decrement(const QString &column, T amount = 1,
const QVector<UpdateItem> &extra = {}) const;
/*! Get a database connection. */
inline const DatabaseConnection &getConnection() const
{ return m_db; }
inline const Grammar &getGrammar() const
{ return m_grammar; }
/*! Get the current query value bindings as flattened QVector. */
QVector<QVariant> getBindings() const;
inline const BindingsMap &getRawBindings() const
{ return m_bindings; }
inline bool getDistinct() const
{ return m_distinct; }
// TODO check up all code and return references when appropriate silverqx
inline const QStringList &getColumns() const
{ return m_columns; }
inline const QString &getFrom() const
{ return m_from; }
inline const QString &getTable() const
{ return m_from; }
inline const QVector<QSharedPointer<JoinClause>> &getJoins() const
{ return m_joins; }
inline const QVector<WhereConditionItem> &getWheres() const
{ return m_wheres; }
inline const QStringList &getGroups() const
{ return m_groups; }
inline const QVector<HavingConditionItem> &getHavings() const
{ return m_havings; }
inline const QVector<OrderByItem> &getOrders() const
{ return m_orders; }
inline int getLimit() const
{ return m_limit; }
inline int getOffset() const
{ return m_offset; }
/*! Get a new instance of the query builder. */
QSharedPointer<Builder> newQuery() const;
/*! Create a new query instance for nested where condition. */
QSharedPointer<Builder> forNestedWhere() const;
/*! Create a raw database expression. */
Expression raw(const QVariant &value) const;
protected:
bool invalidOperator(const QString &comparison) const;
/*! Add a binding to the query. */
Builder &addBinding(const QVariant &binding,
BindingType type = BindingType::WHERE);
Builder &addBinding(const QVector<QVariant> &bindings,
BindingType type = BindingType::WHERE);
/*! Remove all of the expressions from a list of bindings. */
QVector<QVariant> cleanBindings(const QVector<QVariant> &bindings) const;
/*! Get a new join clause. */
QSharedPointer<JoinClause>
newJoinClause(const Builder &query, const QString &type, const QString &table) const;
/*! Add another query builder as a nested where to the query builder. */
Builder &addNestedWhereQuery(QSharedPointer<Builder> query,
const QString &condition);
private:
std::tuple<bool, QSqlQuery> runSelect() const;
/*! All of the available clause operators. */
const QVector<QString> m_operators {
"=", "<", ">", "<=", ">=", "<>", "!=", "<=>",
"like", "like binary", "not like", "ilike",
"&", "|", "^", "<<", ">>",
"rlike", "not rlike", "regexp", "not regexp",
"~", "~*", "!~", "!~*", "similar to",
"not similar to", "not ilike", "~~*", "!~~*",
};
const DatabaseConnection &m_db;
const Grammar &m_grammar;
/*! Prepared statement bindings. Order is crucial here because of that
QMap with an enum struct is used. */
BindingsMap m_bindings {
{BindingType::SELECT, {}},
{BindingType::FROM, {}},
{BindingType::JOIN, {}},
{BindingType::WHERE, {}},
{BindingType::GROUPBY, {}},
{BindingType::HAVING, {}},
{BindingType::ORDER, {}},
{BindingType::UNION, {}},
{BindingType::UNIONORDER, {}},
};
bool m_distinct = false;
QStringList m_columns {"*"};
QString m_from;
QVector<QSharedPointer<JoinClause>> m_joins;
QVector<WhereConditionItem> m_wheres;
QStringList m_groups;
QVector<HavingConditionItem> m_havings;
QVector<OrderByItem> m_orders;
int m_limit = -1;
int m_offset = -1;
};
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
Builder::increment(const QString &column, const T amount,
const QVector<UpdateItem> &extra) const
{
const auto &expression = QStringLiteral("%1 + %2").arg(column).arg(amount);
QVector<UpdateItem> columns {{column, raw(expression)}};
std::copy(extra.cbegin(), extra.cend(), std::back_inserter(columns));
return update(columns);
}
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
Builder::decrement(const QString &column, const T amount,
const QVector<UpdateItem> &extra) const
{
const auto &expression = QStringLiteral("%1 - %2").arg(column).arg(amount);
QVector<UpdateItem> columns {{column, raw(expression)}};
std::copy(extra.cbegin(), extra.cend(), std::back_inserter(columns));
return update(columns);
}
} // namespace Orm::Query
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // QUERYBUILDER_H

View File

@@ -0,0 +1,17 @@
#include "repositoryfactory.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
RepositoryFactory::RepositoryFactory(EntityManager &em)
: m_em(em)
{}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,54 @@
#ifndef REPOSITORYFACTORY_H
#define REPOSITORYFACTORY_H
#include <QSharedPointer>
#include <typeindex>
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
class BaseRepository;
namespace Orm
{
class EntityManager;
/*! This factory is used to create default repository objects for entities at runtime. */
class RepositoryFactory final
{
Q_DISABLE_COPY(RepositoryFactory)
public:
explicit RepositoryFactory(EntityManager &em);
template<typename Repository>
QSharedPointer<Repository> getRepository() const;
private:
EntityManager &m_em;
mutable std::unordered_map<std::type_index, QSharedPointer<BaseRepository>> m_repositoryList;
};
template<typename Repository>
QSharedPointer<Repository> RepositoryFactory::getRepository() const
{
std::type_index index(typeid (Repository));
if (m_repositoryList.count(index) == 0) {
auto repository = QSharedPointer<Repository>::create(m_em);
m_repositoryList[index] = repository;
return repository;
}
return m_repositoryList.at(index).dynamicCast<Repository>();
}
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // REPOSITORYFACTORY_H

View File

@@ -0,0 +1,12 @@
#include "basemodel.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
} // namespace Orm::Tiny
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

781
src/orm/tiny/basemodel.h Normal file
View File

@@ -0,0 +1,781 @@
#ifndef BASEMODEL_H
#define BASEMODEL_H
#include <range/v3/algorithm/contains.hpp>
#include <range/v3/algorithm/copy.hpp>
#include "orm/databaseconnection.h"
#include "orm/ormerror.h"
#include "orm/query/querybuilder.h"
#include "orm/tiny/relations/belongsto.h"
#include "orm/tiny/relations/hasone.h"
#include "orm/tiny/relations/hasmany.h"
#include "orm/tiny/tinybuilder.h"
#include "orm/utils/string.h"
#include "orm/utils/type.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm
{
class DatabaseConnection;
namespace Query
{
class Builder;
}
namespace Tiny
{
// TODO decide/unify when to use class/typename keywords for templates silverqx
template<typename Model>
class Builder;
template<typename Model, typename ...AllRelations>
using TinyBuilder = Builder<Model>;
using QueryBuilder = Query::Builder;
// TODO add concept, AllRelations can not contain type defined in "Model" parameter silverqx
template<typename Model, typename ...AllRelations>
class BaseModel
{
public:
/*! The "type" of the primary key ID. */
using KeyType = quint64;
explicit BaseModel(const QVector<AttributeItem> &attributes = {});
// TODO inline static method vs constexpr static, check it silverqx
/*! Begin querying the model. */
inline static std::unique_ptr<TinyBuilder<Model>> query()
{ return Model().newQuery(); }
/*! Begin querying a model with eager loading. */
static std::unique_ptr<TinyBuilder<Model>>
with(const QVector<WithItem> &relations);
/*! Begin querying a model with eager loading. */
inline static std::unique_ptr<TinyBuilder<Model>>
with(const QString &relation)
{ return with(QVector<WithItem> {{relation}}); }
/*! Get a new query builder for the model's table. */
inline std::unique_ptr<TinyBuilder<Model>> newQuery()
{ return newQueryWithoutScopes(); }
/*! Get a new query builder that doesn't have any global scopes. */
std::unique_ptr<TinyBuilder<Model>> newQueryWithoutScopes();
/*! Get a new query builder that doesn't have any global scopes or eager loading. */
std::unique_ptr<TinyBuilder<Model>> newModelQuery();
/*! Create a new Tiny query builder for the model. */
std::unique_ptr<TinyBuilder<Model>>
newTinyBuilder(const QSharedPointer<QueryBuilder> query);
/*! Create a new model instance that is existing. */
Model newFromBuilder(const QVector<AttributeItem> &attributes = {},
const std::optional<QString> connection = std::nullopt);
/*! Create a new instance of the given model. */
Model newInstance(const QVector<AttributeItem> &attributes = {},
bool exists = false);
/*! Static cast this to a child's instance type instance (CRTP). */
inline Model &model()
{ return static_cast<Model &>(*this); }
/*! Static cast this to a child's instance type (CRTP), const version. */
inline const Model &model() const
{ return static_cast<const Model &>(*this); }
/*! Get the current connection name for the model. */
inline const QString &getConnectionName() const
{ return model().u_connection; }
/*! Get the database connection for the model. */
inline DatabaseConnection &getConnection() const
{ return DatabaseConnection::instance(); }
/*! Set the connection associated with the model. */
inline Model &setConnection(const QString &name)
{ model().u_connection = name; return model(); }
/*! Set the table associated with the model. */
inline Model &setTable(const QString &value)
{ model().u_table = value; return model(); }
/*! Get the table associated with the model. */
inline const QString &getTable() const
{ return model().u_table; }
/*! Get the primary key for the model. */
inline const QString &getKeyName() const
{ return model().u_primaryKey; }
/*! Get the value of the model's primary key. */
inline QVariant getKey() const
{ return getAttribute(getKeyName()); }
/*! Fill the model with an array of attributes. */
Model &fill(const QVector<AttributeItem> &attributes);
/*! Indicates if the model exists. */
bool exists = false;
/* HasAttributes */
/*! Set a given attribute on the model. */
Model &setAttribute(const QString &key, const QVariant &value);
/*! Set the array of model attributes. No checking is done. */
Model &setRawAttributes(const QVector<AttributeItem> &attributes,
bool sync = false);
/*! Sync the original attributes with the current. */
Model &syncOriginal();
/*! Get all of the current attributes on the model. */
const QVector<AttributeItem> &getAttributes() const;
/*! Get an attribute from the model. */
QVariant getAttribute(const QString &key) const;
/*! Get a plain attribute (not a relationship). */
QVariant getAttributeValue(const QString &key) const;
/*! Get an attribute from the $attributes array. */
QVariant getAttributeFromArray(const QString &key) const;
/*! Transform a raw model value using mutators, casts, etc. */
QVariant transformModelValue(const QString &key, const QVariant &value) const;
/*! Get a relationship for Many types relation. */
template<class Related,
template<typename> typename Container = QVector>
Container<Related>
getRelationValue(const QString &relation);
/*! Get a relationship for a One type relation. */
template<typename Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool> = true>
std::optional<Related>
getRelationValue(const QString &relation);
/* HasRelationships */
// TODO make getRelation() Container argument compatible with STL containers API silverqx
/*! Get a specified relationship. */
template<typename Related, template<typename> typename Container = QVector>
Container<Related> getRelation(const QString &name);
/*! Get a specified relationship as Related type, for use with HasOne and BelongsTo relation types. */
template<typename Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool> = true>
std::optional<Related> getRelation(const QString &name);
/*! Determine if the given relation is loaded. */
inline bool relationLoaded(const QString &relation) const
{ return m_relations.contains(relation); };
/*! Set the given relationship on the model. */
template<typename Related>
Model &setRelation(const QString &relation, const QVector<Related> &models);
/*! Set the given relationship on the model. */
template<typename Related>
Model &setRelation(const QString &relation, QVector<Related> &&models);
/*! Set the given relationship on the model. */
template<typename Related>
Model &setRelation(const QString &relation, const std::optional<Related> &model);
/*! Set the given relationship on the model. */
template<typename Related>
Model &setRelation(const QString &relation, std::optional<Related> &&model);
/*! Get the default foreign key name for the model. */
QString getForeignKey() const;
/*! Define a one-to-one relationship. */
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
hasOne(QString foreignKey = "", QString localKey = "");
/*! Define an inverse one-to-one or many relationship. */
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
belongsTo(QString foreignKey = "", QString ownerKey = "", QString relation = "");
/*! Define a one-to-many relationship. */
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
hasMany(QString foreignKey = "", QString localKey = "");
/*! Invoke Model::eagerVisitor() to define template argument Related for eagerLoaded relation. */
void eagerLoadRelationVisitor(const WithItem &relation, TinyBuilder<Model> &builder,
QVector<Model> &models);
/*! Get a relation method in the relations hash field defined in the current model instance. */
const std::any &getRelationMethod(const QString &name) const;
protected:
/*! Get a new query builder instance for the connection. */
QSharedPointer<QueryBuilder> newBaseQueryBuilder() const;
/*! Continue execution after a relation type was obtained ( by Related template parameter ). */
template<typename Related>
inline void eagerVisited()
{ m_eagerStore->builder.template eagerLoadRelation<Related>(
m_eagerStore->models, m_eagerStore->relation); }
/* HasAttributes */
/*! Get a relationship value from a method. */
// TODO I think this can be merged to one template method, I want to preserve Orm::One/Many tags and use std::enable_if to switch types by Orm::One/Many tag 🤔 silverqx
template<class Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, Many>, bool> = true>
QVector<Related>
getRelationshipFromMethod(const QString &relation);
/*! Get a relationship value from a method. */
template<class Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool> = true>
std::optional<Related>
getRelationshipFromMethod(const QString &relation);
/* HasRelationships */
/*! Create a new model instance for a related model. */
template<typename Related>
Related newRelatedInstance() const;
// TODO can be unified to a one templated method by relation type silverqx
/*! Instantiate a new HasOne relationship. */
template<typename Related>
inline std::unique_ptr<Relations::Relation<Model, Related>>
newHasOne(std::unique_ptr<TinyBuilder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey) const
{ return Relations::HasOne<Model, Related>::create(
std::move(query), parent, foreignKey, localKey); }
/*! Instantiate a new BelongsTo relationship. */
template<typename Related>
inline std::unique_ptr<Relations::Relation<Model, Related>>
newBelongsTo(std::unique_ptr<TinyBuilder<Related>> &&query,
Model &child, const QString &foreignKey,
const QString &ownerKey, const QString &relation) const
{ return Relations::BelongsTo<Model, Related>::create(
std::move(query), child, foreignKey, ownerKey, relation); }
/*! Instantiate a new HasMany relationship. */
template<typename Related>
inline std::unique_ptr<Relations::Relation<Model, Related>>
newHasMany(std::unique_ptr<TinyBuilder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey) const
{ return Relations::HasMany<Model, Related>::create(
std::move(query), parent, foreignKey, localKey); }
/*! Guess the "belongs to" relationship name. */
template<typename Related>
QString guessBelongsToRelation() const;
/*! The connection name for the model. */
QString u_connection {""};
/*! The primary key for the model. */
QString u_primaryKey {"id"};
// TODO for std::any check, whether is appropriate to define template requirement std::is_nothrow_move_constructible ( to avoid dynamic allocations for small objects and how this internally works ) silverqx
/*! Map of relation names to methods. */
QHash<QString, std::any> u_relations;
/*! The relations to eager load on every query. */
QVector<WithItem> u_with;
/*! The relationship counts that should be eager loaded on every query. */
QVector<WithItem> u_withCount;
/* HasAttributes */
// TODO should be QHash, I choosen QVector, becuase I wanted to preserve attributes order, think about this, would be solution to use undered_map which preserves insert order? and do I really need to preserve insert order? 🤔, the same is true for m_original field silverqx
/*! The model's attributes. */
QVector<AttributeItem> m_attributes;
/*! The model attribute's original state. */
QVector<AttributeItem> m_original;
/* HasRelationships */
/*! The loaded relationships for the model. */
QHash<QString,
std::variant<QVector<AllRelations>..., std::optional<AllRelations>...>> m_relations;
private:
/* HasRelationships */
/*! Throw exception if a relation is not defined. */
void validateUserRelation(const QString &name) const;
/*! Helps to avoid passing variables to the Model::eagerVisitor(). */
struct EagerRelationStoreItem
{
const WithItem &relation;
TinyBuilder<Model> &builder;
QVector<Model> &models;
};
/*! Store to save values before Model::eagerVisitor() is called. */
const EagerRelationStoreItem *m_eagerStore = nullptr;
};
template<typename Model, typename ...AllRelations>
BaseModel<Model, AllRelations...>::BaseModel(const QVector<AttributeItem> &attributes)
{
// Compile time check if a primary key type is supported by a QVariant
qMetaTypeId<typename Model::KeyType>();
syncOriginal();
fill(attributes);
}
template<typename Model, typename ...AllRelations>
std::unique_ptr<TinyBuilder<Model>>
BaseModel<Model, AllRelations...>::with(const QVector<WithItem> &relations)
{
auto builder = query();
builder->with(relations);
return builder;
}
template<typename Model, typename ...AllRelations>
std::unique_ptr<TinyBuilder<Model>>
BaseModel<Model, AllRelations...>::newQueryWithoutScopes()
{
auto tinyBuilder = newModelQuery();
tinyBuilder->with(model().u_with);
return tinyBuilder;
}
template<typename Model, typename ...AllRelations>
std::unique_ptr<TinyBuilder<Model>>
BaseModel<Model, AllRelations...>::newModelQuery()
{
return newTinyBuilder(newBaseQueryBuilder());
}
template<typename Model, typename ...AllRelations>
std::unique_ptr<TinyBuilder<Model>>
BaseModel<Model, AllRelations...>::newTinyBuilder(const QSharedPointer<QueryBuilder> query)
{
return std::make_unique<TinyBuilder<Model>>(query, model());
}
template<typename Model, typename ...AllRelations>
Model
BaseModel<Model, AllRelations...>::newFromBuilder(const QVector<AttributeItem> &attributes,
const std::optional<QString> connection)
{
auto model = newInstance({}, true);
model.setRawAttributes(attributes, true);
model.setConnection(connection ? *connection : getConnectionName());
return model;
}
template<typename Model, typename ...AllRelations>
Model
BaseModel<Model, AllRelations...>::newInstance(const QVector<AttributeItem> &attributes,
const bool exists)
{
/* This method just provides a convenient way for us to generate fresh model
instances of this current model. It is particularly useful during the
hydration of new objects via the Eloquent query builder instances. */
Model model(attributes);
model.exists = exists;
model.setConnection(getConnectionName());
model.setTable(getTable());
return model;
}
template<typename Model, typename ...AllRelations>
QSharedPointer<QueryBuilder>
BaseModel<Model, AllRelations...>::newBaseQueryBuilder() const
{
return getConnection().queryBuilder();
}
template<typename Model, typename ...AllRelations>
template<class Related, template<typename> typename Container>
Container<Related>
BaseModel<Model, AllRelations...>::getRelationValue(const QString &relation)
{
/*! If the key already exists in the relationships array, it just means the
relationship has already been loaded, so we'll just return it out of
here because there is no need to query within the relations twice. */
if (relationLoaded(relation)) {
// TODO duplicate silverqx
Container<Related> relatedModels;
for (auto &v : std::get<QVector<Related>>(m_relations.find(relation).value()))
relatedModels.push_back(v);
return relatedModels;
}
/*! If the "attribute" exists as a method on the model, we will just assume
it is a relationship and will load and return results from the query
and hydrate the relationship's value on the "relationships" array. */
// if (method_exists($this, $key) ||
// (static::$relationResolvers[get_class($this)][$key] ?? null)) {
// return $this->getRelationshipFromMethod($key);
// }
// Check, if this relation is defined
if (model().u_relations.contains(relation))
return getRelationshipFromMethod<Related, Many>(relation);
return {};
}
template<typename Model, typename ...AllRelations>
template<typename Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool>>
std::optional<Related>
BaseModel<Model, AllRelations...>::getRelationValue(const QString &relation)
{
/*! If the key already exists in the relationships array, it just means the
relationship has already been loaded, so we'll just return it out of
here because there is no need to query within the relations twice. */
if (relationLoaded(relation))
return std::get<std::optional<Related>>(m_relations.find(relation).value());
// Check, if a relation is defined and if it is, get it
if (model().u_relations.contains(relation))
return getRelationshipFromMethod<Related, One>(relation);
return std::nullopt;
}
template<typename Model, typename ...AllRelations>
template<class Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, Many>, bool>>
QVector<Related>
BaseModel<Model, AllRelations...>::getRelationshipFromMethod(const QString &relationName)
{
// Obtain related model
auto relatedModel = std::get<QVector<Related>>(
std::invoke(
std::any_cast<RelationType<Model, Related>>(model().u_relations[relationName]),
model())
->getResults());
setRelation(relationName, relatedModel);
return relatedModel;
}
template<typename Model, typename ...AllRelations>
template<class Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool>>
std::optional<Related>
BaseModel<Model, AllRelations...>::getRelationshipFromMethod(const QString &relationName)
{
// Obtain related model
auto relatedModel = std::get<std::optional<Related>>(
std::invoke(
std::any_cast<RelationType<Model, Related>>(model().u_relations[relationName]),
model())
->getResults());
setRelation(relationName, relatedModel);
return relatedModel;
}
// TODO solve different behavior like Eloquent getRelation() silverqx
// TODO next many relation compiles with Orm::One and exception during runtime occures, solve this during compile, One relation only with Orm::One and many relation type only with Container version silverqx
template<typename Model, typename ...AllRelations>
template<typename Related, template<typename> typename Container>
Container<Related>
BaseModel<Model, AllRelations...>::getRelation(const QString &name)
{
if (!relationLoaded(name))
// TODO create RelationError class silverqx
throw OrmError("Undefined relation key (in m_relations) : " + name);
// TODO make it vector or pointers/references to models (relations), may be not needed to modify models and call save()/push() silverqx
Container<Related> relatedModels;
ranges::copy(std::get<QVector<Related>>(m_relations.find(name).value()),
ranges::back_inserter(relatedModels));
return relatedModels;
}
template<typename Model, typename ...AllRelations>
template<typename Related, typename Tag,
std::enable_if_t<std::is_same_v<Tag, One>, bool>>
std::optional<Related>
BaseModel<Model, AllRelations...>::getRelation(const QString &name)
{
// TODO duplicated if statement silverqx
if (!relationLoaded(name))
// TODO create RelationError class silverqx
throw OrmError("Undefined relation key (in m_relations) : " + name);
// TODO instantiate relation by name and check if is_base_of OneRelation/ManyRelation, to have nice exception message (in debug mode only), because is impossible to check this during compile time silverqx
// TODO should I return references to m_relations or copies? decide when I will implement save/push to synchronize changed attributes back to db silverqx
return std::get<std::optional<Related>>(m_relations.find(name).value());
}
template<typename Model, typename ...AllRelations>
template<typename Related>
Model &
BaseModel<Model, AllRelations...>::setRelation(const QString &relation,
const QVector<Related> &models)
{
m_relations.insert(relation, models);
return model();
}
template<typename Model, typename ...AllRelations>
template<typename Related>
Model &
BaseModel<Model, AllRelations...>::setRelation(const QString &relation,
QVector<Related> &&models)
{
m_relations.insert(relation, std::move(models));
return model();
}
// TODO next unify setRelation() methods silverqx
template<typename Model, typename ...AllRelations>
template<typename Related>
Model &
BaseModel<Model, AllRelations...>::setRelation(const QString &relation,
const std::optional<Related> &model)
{
m_relations.insert(relation, model);
return this->model();
}
template<typename Model, typename ...AllRelations>
template<typename Related>
Model &
BaseModel<Model, AllRelations...>::setRelation(const QString &relation,
std::optional<Related> &&model)
{
m_relations.insert(relation, std::move(model));
return this->model();
}
template<typename Model, typename ...AllRelations>
Model &
BaseModel<Model, AllRelations...>::fill(const QVector<AttributeItem> &attributes)
{
for (const auto &attribute : attributes)
setAttribute(attribute.key, attribute.value);
return model();
}
template<typename Model, typename ...AllRelations>
Model &BaseModel<Model, AllRelations...>::setAttribute(
const QString &key, const QVariant &value)
{
m_attributes.append({key, value});
return model();
}
template<typename Model, typename ...AllRelations>
Model &
BaseModel<Model, AllRelations...>::setRawAttributes(
const QVector<AttributeItem> &attributes,
const bool sync)
{
m_attributes = attributes;
if (sync)
syncOriginal();
return model();
}
template<typename Model, typename ...AllRelations>
Model &BaseModel<Model, AllRelations...>::syncOriginal()
{
m_original = getAttributes();
return model();
}
template<typename Model, typename ...AllRelations>
const QVector<AttributeItem> &
BaseModel<Model, AllRelations...>::getAttributes() const
{
/*mergeAttributesFromClassCasts();*/
return m_attributes;
}
template<typename Model, typename ...AllRelations>
QVariant BaseModel<Model, AllRelations...>::getAttribute(const QString &key) const
{
if (key.isEmpty() || key.isNull())
return {};
const auto containsKey = ranges::contains(m_attributes, true,
[&key](const auto &attribute)
{
return attribute.key == key;
});
/* If the attribute exists in the attribute array or has a "get" mutator we will
get the attribute's value. Otherwise, we will proceed as if the developers
are asking for a relationship's value. This covers both types of values. */
if (containsKey
// || array_key_exists($key, $this->casts)
// || hasGetMutator(key)
// || isClassCastable(key)
)
return getAttributeValue(key);
return {};
// TODO Eloquent returns relation when didn't find attribute, decide how to solve this, or add NOTE about different api silverqx
// return $this->getRelationValue($key);
}
template<typename Model, typename ...AllRelations>
QVariant BaseModel<Model, AllRelations...>::getAttributeValue(const QString &key) const
{
return transformModelValue(key, getAttributeFromArray(key));
}
// TODO candidate for optional const reference, to be able return null value and use reference at the same time silverqx
template<typename Model, typename ...AllRelations>
QVariant BaseModel<Model, AllRelations...>::getAttributeFromArray(const QString &key) const
{
const auto &attributes = getAttributes();
const auto itAttribute = ranges::find(attributes, true,
[&key](const auto &attribute)
{
return attribute.key == key;
});
// Not found
if (itAttribute == ranges::end(attributes))
return {};
return itAttribute->value;
}
template<typename Model, typename ...AllRelations>
QVariant BaseModel<Model, AllRelations...>::transformModelValue(
const QString &key,
const QVariant &value) const
{
Q_UNUSED(key)
return value;
}
template<typename Model, typename ...AllRelations>
QString BaseModel<Model, AllRelations...>::getForeignKey() const
{
return QStringLiteral("%1_%2").arg(
Utils::String::toSnake(Utils::Type::classPureBasename<decltype (model())>()),
getKeyName());
}
template<typename Model, typename ...AllRelations>
template<typename Related>
Related BaseModel<Model, AllRelations...>::newRelatedInstance() const
{
Related instance;
if (instance.getConnectionName().isEmpty())
instance.setConnection(getConnectionName());
return instance;
}
template<typename Model, typename ...AllRelations>
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
BaseModel<Model, AllRelations...>::hasOne(QString foreignKey, QString localKey)
{
auto instance = newRelatedInstance<Related>();
if (foreignKey.isEmpty())
foreignKey = getForeignKey();
if (localKey.isEmpty())
localKey = getKeyName();
return newHasOne<Related>(instance.newQuery(), model(),
instance.getTable() + '.' + foreignKey, localKey);
}
template<typename Model, typename ...AllRelations>
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
BaseModel<Model, AllRelations...>::belongsTo(QString foreignKey, QString ownerKey,
QString relation)
{
/* If no relation name was given, we will use the Related class type to extract
the name and use that as the relationship name as most of the time this
will be what we desire to use for the relationships. */
if (relation.isEmpty())
relation = guessBelongsToRelation<Related>();
auto instance = newRelatedInstance<Related>();
const auto &primaryKey = instance.getKeyName();
/* If no foreign key was supplied, we can use a backtrace to guess the proper
foreign key name by using the name of the relationship function, which
when combined with an "_id" should conventionally match the columns. */
if (foreignKey.isEmpty())
foreignKey = Utils::String::toSnake(relation) + '_' + primaryKey;
/* Once we have the foreign key names, we'll just create a new Eloquent query
for the related models and returns the relationship instance which will
actually be responsible for retrieving and hydrating every relations. */
if (ownerKey.isEmpty())
ownerKey = primaryKey;
return newBelongsTo<Related>(instance.newQuery(), model(),
foreignKey, ownerKey, relation);
}
template<typename Model, typename ...AllRelations>
template<typename Related>
std::unique_ptr<Relations::Relation<Model, Related>>
BaseModel<Model, AllRelations...>::hasMany(QString foreignKey, QString localKey)
{
auto instance = newRelatedInstance<Related>();
if (foreignKey.isEmpty())
foreignKey = getForeignKey();
if (localKey.isEmpty())
localKey = getKeyName();
return newHasMany<Related>(instance.newQuery(), model(),
instance.getTable() + '.' + foreignKey, localKey);
}
template<typename Model, typename ...AllRelations>
template<typename Related>
QString BaseModel<Model, AllRelations...>::guessBelongsToRelation() const
{
auto relation = Utils::Type::classPureBasename<Related>();
relation[0] = relation[0].toLower();
return relation;
}
template<typename Model, typename ...AllRelations>
void BaseModel<Model, AllRelations...>::eagerLoadRelationVisitor(
const WithItem &relation, TinyBuilder<Model> &builder, QVector<Model> &models)
{
// Throw excpetion if a relation is not defined
validateUserRelation(relation.name);
EagerRelationStoreItem eagerStore {relation, builder, models};
m_eagerStore = &eagerStore;
model().eagerVisitor(relation.name);
}
template<typename Model, typename ...AllRelations>
const std::any &
BaseModel<Model, AllRelations...>::getRelationMethod(const QString &name) const
{
// Throw excpetion if a relation is not defined
validateUserRelation(name);
return model().u_relations.find(name).value();
}
template<typename Model, typename ...AllRelations>
void BaseModel<Model, AllRelations...>::validateUserRelation(const QString &name) const
{
if (!model().u_relations.contains(name))
throw OrmError("Undefined relation key (in relations) : " + name);
}
} // namespace Orm::Tiny
} // namespace Orm
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // BASEMODEL_H

View File

@@ -0,0 +1,12 @@
#include "hasattributes.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
} // namespace Orm::Tiny
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,61 @@
#ifndef HASATTRIBUTES_H
#define HASATTRIBUTES_H
#include "orm/ormtypes.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
template<typename Model>
class HasAttributes
{
public:
/*! Set a given attribute on the model. */
Model &setAttribute(const QString &key, const QVariant &value);
/*! Set the array of model attributes. No checking is done. */
// Model &setRawAttributes(const QVector<AttributeItem> &attributes,
// bool sync = false);
/*! Get all of the current attributes on the model. */
const QVector<AttributeItem> &getAttributes() const;
protected:
/*! The model's attributes. */
QVector<AttributeItem> m_attributes;
};
template<typename Model>
Model &
HasAttributes<Model>::setAttribute(const QString &key, const QVariant &value)
{
m_attributes.append({key, value});
return static_cast<Model &>(*this);
}
// template<typename Model>
// Model &
// HasAttributes<Model>::setRawAttributes(const QVector<AttributeItem> &attributes,
// const bool sync)
// {
// m_attributes = attributes;
// if (sync)
// syncOriginal();
// return static_cast<Model &>(*this);
// }
template<typename Model>
const QVector<AttributeItem> &HasAttributes<Model>::getAttributes() const
{ return m_attributes; }
} // namespace Orm::Tiny
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // HASATTRIBUTES_H

View File

@@ -0,0 +1,12 @@
#include "belongsto.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,224 @@
#ifndef BELONGSTO_H
#define BELONGSTO_H
#include <QDebug>
#include <range/v3/action/sort.hpp>
#include <range/v3/action/unique.hpp>
#include "orm/tiny/relations/relation.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
template<class Model, class Related>
class BelongsTo : public Relation<Model, Related>, public OneRelation
{
public:
/*! Instantiate and initialize a new HasOne instance. */
static std::unique_ptr<Relation<Model, Related>>
create(std::unique_ptr<Builder<Related>> &&query,
Model &child, const QString &foreignKey,
const QString &ownerKey, const QString &relation);
/*! Set the base constraints on the relation query. */
void addConstraints() const override;
/*! Set the constraints for an eager load of the relation. */
void addEagerConstraints(const QVector<Model> &models) const override;
/*! Initialize the relation on a set of models. */
QVector<Model> &
initRelation(QVector<Model> &models, const QString &relation) const override;
/*! Match the eagerly loaded results to their parents. */
void match(QVector<Model> &models, QVector<Related> results,
const QString &relation) const override;
/*! Get the results of the relationship. */
std::variant<QVector<Related>, std::optional<Related>>
getResults() const override;
protected:
/*! Gather the keys from an array of related models. */
QVector<QVariant> getEagerModelKeys(const QVector<Model> &models) const;
/*! Get the default value for this relation. */
Model &getDefaultFor(Model &parent) const;
/*! Build model dictionary keyed by the parent's primary key. */
QHash<typename Model::KeyType, Related>
buildDictionary(const QVector<Related> &results) const;
// WARNING don't forget to make them references silverqx
/*! The child model instance of the relation. */
const Model &m_child;
/*! The foreign key of the parent model. */
QString m_foreignKey;
/*! The associated key on the parent model. */
QString m_ownerKey;
/*! The name of the relationship. */
QString m_relationName;
/*! The count of self joins. */
constexpr static int selfJoinCount = 0;
private:
BelongsTo(std::unique_ptr<Builder<Related>> &&query, const Model &child,
const QString &foreignKey, const QString &ownerKey,
const QString &relationName);
};
template<class Model, class Related>
BelongsTo<Model, Related>::BelongsTo(std::unique_ptr<Builder<Related>> &&query,
const Model &child, const QString &foreignKey,
const QString &ownerKey, const QString &relationName)
: Relation<Model, Related>(std::move(query), child)
/* In the underlying base relationship class, this variable is referred to as
the "parent" since most relationships are not inversed. But, since this
one is we will create a "child" variable for much better readability. */
, m_child(child)
, m_foreignKey(foreignKey)
, m_ownerKey(ownerKey)
, m_relationName(relationName)
{}
template<class Model, class Related>
std::unique_ptr<Relation<Model, Related>>
BelongsTo<Model, Related>::create(std::unique_ptr<Builder<Related>> &&query,
Model &child, const QString &foreignKey,
const QString &ownerKey, const QString &relation)
{
auto instance = std::unique_ptr<BelongsTo<Model, Related>>(
new BelongsTo(std::move(query), child, foreignKey, ownerKey, relation));
instance->init();
return instance;
}
template<class Model, class Related>
void BelongsTo<Model, Related>::addConstraints() const
{
if (!this->constraints)
return;
/* For belongs to relationships, which are essentially the inverse of has one
or has many relationships, we need to actually query on the primary key
of the related models matching on the foreign key that's on a parent. */
const auto &table = this->m_related.getTable();
this->m_query->where(table + '.' + m_ownerKey, QStringLiteral("="),
m_child.getAttribute(m_foreignKey));
}
template<class Model, class Related>
void BelongsTo<Model, Related>::addEagerConstraints(const QVector<Model> &models) const
{
/* We'll grab the primary key name of the related models since it could be set to
a non-standard name and not "id". We will then construct the constraint for
our eagerly loading query so it returns the proper models from execution. */
this->m_query->getQuery().whereIn(this->m_related.getTable() + '.' + m_ownerKey,
getEagerModelKeys(models));
}
template<class Model, class Related>
QVector<Model> &
BelongsTo<Model, Related>::initRelation(QVector<Model> &models,
const QString &relation) const
{
for (auto &model : models)
model.template setRelation<Related>(relation, std::nullopt);
return models;
}
template<class Model, class Related>
void BelongsTo<Model, Related>::match(QVector<Model> &models,
QVector<Related> results,
const QString &relation) const
{
/* First we will get to build a dictionary of the child models by their primary
key of the relationship, then we can easily match the children back onto
the parents using that dictionary and the primary key of the children. */
const auto dictionary = buildDictionary(results);
/* Once we have the dictionary constructed, we can loop through all the parents
and match back onto their children using these keys of the dictionary and
the primary key of the children to map them onto the correct instances. */
for (auto &model : models) {
const auto foreign = model.getAttribute(m_foreignKey)
.template value<typename Model::KeyType>();
if (dictionary.contains(foreign))
model.setRelation(relation,
std::optional<Related>(dictionary.find(foreign).value()));
}
}
template<class Model, class Related>
QHash<typename Model::KeyType, Related>
BelongsTo<Model, Related>::buildDictionary(const QVector<Related> &results) const
{
QHash<typename Model::KeyType, Related> dictionary;
/*! Build model dictionary keyed by the parent's primary key. */
for (const auto &result : results)
dictionary.insert(result.getAttribute(m_ownerKey)
.template value<typename Model::KeyType>(),
result);
return dictionary;
}
template<class Model, class Related>
std::variant<QVector<Related>, std::optional<Related>>
BelongsTo<Model, Related>::getResults() const
{
// Model doesn't contain foreign key ( eg empty Model instance )
if (const auto foreign = m_child.getAttribute(m_foreignKey);
!foreign.isValid() || foreign.isNull()
)
return std::nullopt;
// TODO add support for getDefaultFor() silverqx
const auto first = this->m_query->first();
// TODO check if I can return reference silverqx
return first ? first : std::nullopt;
}
template<class Model, class Related>
QVector<QVariant>
BelongsTo<Model, Related>::getEagerModelKeys(const QVector<Model> &models) const
{
QVector<QVariant> keys;
/* First we need to gather all of the keys from the parent models so we know what
to query for via the eager loading query. We will add them to an array then
execute a "where in" statement to gather up all of those related records. */
for (const auto &model : models) {
const auto &value = model.getAttribute(m_foreignKey);
if (!value.isNull())
// TODO add support for non-int primary keys, ranges::acrions doesn't accept QVariant container silverqx
keys.append(value);
}
using namespace ranges;
return keys |= actions::sort(less {}, &QVariant::value<typename Model::KeyType>)
| actions::unique;
}
template<class Model, class Related>
Model &BelongsTo<Model, Related>::getDefaultFor(Model &parent) const
{
Q_UNUSED(parent)
return parent;
}
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // BELONGSTO_H

View File

@@ -0,0 +1,12 @@
#include "hasmany.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,91 @@
#ifndef HASMANY_H
#define HASMANY_H
#include "orm/tiny/relations/hasoneormany.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
template<class Model, class Related>
class HasMany : public HasOneOrMany<Model, Related>, public ManyRelation
{
public:
/*! Instantiate and initialize a new HasMany instance. */
static std::unique_ptr<Relation<Model, Related>>
create(std::unique_ptr<Builder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey);
/*! Initialize the relation on a set of models. */
QVector<Model> &
initRelation(QVector<Model> &models, const QString &relation) const override;
/*! Match the eagerly loaded results to their parents. */
inline void match(QVector<Model> &models, QVector<Related> results,
const QString &relation) const override
{ this->template matchOneOrMany<QVector<Related>>(models, results, relation); }
/*! Get the results of the relationship. */
std::variant<QVector<Related>, std::optional<Related>>
getResults() const override;
private:
HasMany(std::unique_ptr<Builder<Related>> &&query,
const Model &parent,
const QString &foreignKey, const QString &localKey);
};
template<class Model, class Related>
HasMany<Model, Related>::HasMany(std::unique_ptr<Builder<Related>> &&query,
const Model &parent,
const QString &foreignKey, const QString &localKey)
: HasOneOrMany<Model, Related>(std::move(query), parent, foreignKey, localKey)
{}
template<class Model, class Related>
std::unique_ptr<Relation<Model, Related>>
HasMany<Model, Related>::create(std::unique_ptr<Builder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey)
{
auto instance = std::unique_ptr<HasMany<Model, Related>>(
new HasMany(std::move(query), parent, foreignKey, localKey));
instance->init();
return instance;
}
template<class Model, class Related>
QVector<Model> &
HasMany<Model, Related>::initRelation(QVector<Model> &models,
const QString &relation) const
{
for (auto &model : models)
model.template setRelation<Related>(relation, QVector<Related>());
// TODO add support for default models (trait SupportsDefaultModels) silverqx
// model.setRelation(relation, getDefaultFor(model));
return models;
}
template<class Model, class Related>
std::variant<QVector<Related>, std::optional<Related>>
HasMany<Model, Related>::getResults() const
{
// Model doesn't contain primary key ( eg empty Model instance )
if (const auto key = this->getParentKey();
!key.isValid() || key.isNull()
)
return QVector<Related>();
return this->m_query->get();
}
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // HASMANY_H

View File

@@ -0,0 +1,12 @@
#include "hasone.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,88 @@
#ifndef HASONE_H
#define HASONE_H
#include "orm/tiny/relations/hasoneormany.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
template<class Model, class Related>
class HasOne : public HasOneOrMany<Model, Related>, OneRelation
{
public:
/*! Instantiate and initialize a new HasOne instance. */
static std::unique_ptr<Relation<Model, Related>>
create(std::unique_ptr<Builder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey);
/*! Initialize the relation on a set of models. */
QVector<Model> &
initRelation(QVector<Model> &models, const QString &relation) const override;
/*! Match the eagerly loaded results to their parents. */
inline void
match(QVector<Model> &models, QVector<Related> results, const QString &relation) const override
{ this->template matchOneOrMany<std::optional<Related>>(models, results, relation); }
/*! Get the results of the relationship. */
std::variant<QVector<Related>, std::optional<Related>>
getResults() const override;
protected:
HasOne(std::unique_ptr<Builder<Related>> &&query, const Model &parent,
const QString &foreignKey, const QString &localKey);
};
template<class Model, class Related>
HasOne<Model, Related>::HasOne(std::unique_ptr<Builder<Related>> &&query, const Model &parent,
const QString &foreignKey, const QString &localKey)
: HasOneOrMany<Model, Related>(std::move(query), parent, foreignKey, localKey)
{}
template<class Model, class Related>
std::unique_ptr<Relation<Model, Related>>
HasOne<Model, Related>::create(std::unique_ptr<Builder<Related>> &&query, Model &parent,
const QString &foreignKey, const QString &localKey)
{
auto instance = std::unique_ptr<HasOne<Model, Related>>(
new HasOne(std::move(query), parent, foreignKey, localKey));
instance->init();
return instance;
}
template<class Model, class Related>
QVector<Model> &
HasOne<Model, Related>::initRelation(QVector<Model> &models, const QString &relation) const
{
for (auto &model : models)
model.template setRelation<Related>(relation, std::nullopt);
return models;
}
template<class Model, class Related>
std::variant<QVector<Related>, std::optional<Related>>
HasOne<Model, Related>::getResults() const
{
// Model doesn't contain primary key ( eg empty Model instance )
if (const auto key = this->getParentKey();
!key.isValid() || key.isNull()
)
return std::nullopt;
const auto first = this->m_query->first();
return first ? first : std::nullopt;
}
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // HASONE_H

View File

@@ -0,0 +1,12 @@
#include "hasoneormany.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,157 @@
#ifndef HASONEORMANY_H
#define HASONEORMANY_H
#include <range/v3/action/sort.hpp>
#include <range/v3/action/unique.hpp>
#include "orm/tiny/relations/relation.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
template<class Model, class Related>
class HasOneOrMany : public Relation<Model, Related>
{
public:
HasOneOrMany(std::unique_ptr<Builder<Related>> &&query,
const Model &parent,
const QString &foreignKey, const QString &localKey);
/*! Set the base constraints on the relation query. */
void addConstraints() const override;
/*! Set the constraints for an eager load of the relation. */
void addEagerConstraints(const QVector<Model> &models) const override;
/*! Get the key value of the parent's local key. */
QVariant getParentKey() const
{ return this->m_parent.getAttribute(m_localKey); }
protected:
/*! Get all of the primary keys for an array of models. */
QVector<QVariant>
getKeys(const QVector<Model> &models, const QString &key = "") const;
/*! Match the eagerly loaded results to their many parents. */
template<typename RelationValue>
void matchOneOrMany(QVector<Model> &models, QVector<Related> &results,
const QString &relation) const;
/*! Build model dictionary keyed by the relation's foreign key. */
template<typename HashValue>
QHash<typename Model::KeyType, HashValue>
buildDictionary(QVector<Related> &results) const;
/*! Get the plain foreign key. */
QString getForeignKeyName() const;
/*! Get the foreign key for the relationship. */
inline const QString &getQualifiedForeignKeyName() const
{ return m_foreignKey; }
// WARNING don't forget to make them references silverqx
/*! The foreign key of the parent model. */
QString m_foreignKey;
/*! The local key of the parent model. */
QString m_localKey;
// TODO next use inline static (better constexpr?) when appropriate, check all the code silverqx
/*! The count of self joins. */
constexpr static int selfJoinCount = 0;
};
template<class Model, class Related>
HasOneOrMany<Model, Related>::HasOneOrMany(std::unique_ptr<Builder<Related>> &&query,
const Model &parent,
const QString &foreignKey, const QString &localKey)
: Relation<Model, Related>(std::move(query), parent)
, m_foreignKey(foreignKey)
, m_localKey(localKey)
{}
template<class Model, class Related>
void HasOneOrMany<Model, Related>::addConstraints() const
{
if (!this->constraints)
return;
this->m_query->where(m_foreignKey, QStringLiteral("="), getParentKey());
this->m_query->whereNotNull(m_foreignKey);
}
template<class Model, class Related>
void HasOneOrMany<Model, Related>::addEagerConstraints(const QVector<Model> &models) const
{
this->m_query->getQuery().whereIn(m_foreignKey, getKeys(models, m_localKey));
}
template<class Model, class Related>
QVector<QVariant>
HasOneOrMany<Model, Related>::getKeys(const QVector<Model> &models, const QString &key) const
{
QVector<QVariant> keys;
for (const auto &model : models)
keys.append(key.isEmpty() ? model.getKey()
: model.getAttribute(key));
using namespace ranges;
return keys |= actions::sort(less {}, &QVariant::value<typename Model::KeyType>)
| actions::unique;
}
template<class Model, class Related>
template<typename RelationValue>
void HasOneOrMany<Model, Related>::matchOneOrMany(
QVector<Model> &models, QVector<Related> &results,
const QString &relation) const
{
auto dictionary = buildDictionary<RelationValue>(results);
/* Once we have the dictionary we can simply spin through the parent models to
link them up with their children using the keyed dictionary to make the
matching very convenient and easy work. Then we'll just return them. */
for (auto &model : models) {
if (const auto &key = model.getAttribute(m_localKey)
.template value<typename Model::KeyType>();
dictionary.contains(key)
)
model.setRelation(relation,
std::move(dictionary.find(key).value()));
}
}
template<class Model, class Related>
template<typename RelationValue>
QHash<typename Model::KeyType, RelationValue>
HasOneOrMany<Model, Related>::buildDictionary(QVector<Related> &results) const
{
QHash<typename Model::KeyType, RelationValue> dictionary;
for (auto &result : results)
if constexpr (
const auto &foreign = result.getAttribute(getForeignKeyName())
.template value<typename Model::KeyType>();
std::is_same_v<RelationValue, QVector<Related>>
)
dictionary[foreign].append(std::move(result));
else
dictionary.insert(foreign, std::move(result));
return dictionary;
}
template<class Model, class Related>
QString HasOneOrMany<Model, Related>::getForeignKeyName() const
{
const auto segments = getQualifiedForeignKeyName().splitRef(QChar('.'));
return segments.last().toString();
}
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // HASONEORMANY_H

View File

@@ -0,0 +1,12 @@
#include "relation.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny::Relations
{
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

View File

@@ -0,0 +1,109 @@
#ifndef RELATION_H
#define RELATION_H
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
template<class Model>
class Builder;
template<class Model, typename ...AllRelations>
class BaseModel;
}
namespace Orm::Tiny::Relations
{
template<class Model, class Related>
class Relation
{
public:
Relation(std::unique_ptr<Builder<Related>> &&query, const Model &parent);
virtual ~Relation() = default;
/*! Set the base constraints on the relation query. */
virtual void addConstraints() const = 0;
/*! Run a callback with constraints disabled on the relation. */
static std::unique_ptr<Relation<Model, Related>>
noConstraints(const std::function<std::unique_ptr<Relation<Model, Related>>()> &callback);
/*! Set the constraints for an eager load of the relation. */
virtual void addEagerConstraints(const QVector<Model> &models) const = 0;
/*! Initialize the relation on a set of models. */
virtual QVector<Model> &
initRelation(QVector<Model> &models, const QString &relation) const = 0;
/*! Match the eagerly loaded results to their parents. */
virtual void match(QVector<Model> &models, QVector<Related> results,
const QString &relation) const = 0;
/*! Get the results of the relationship. */
virtual std::variant<QVector<Related>, std::optional<Related>>
getResults() const = 0;
/*! Get the relationship for eager loading. */
inline QVector<Related> getEager() const
{ return get(); }
/*! Execute the query as a "select" statement. */
inline QVector<Related> get(const QStringList columns = {"*"}) const
{ return m_query->get(columns); }
/*! Get the underlying query for the relation. */
Builder<Related> &getQuery()
{ return *m_query; }
protected:
/*! Initialize a Relation instance. */
void init() const
{ addConstraints(); }
// WARNING don't forget to make them references silverqx
/*! The Eloquent query builder instance. */
std::shared_ptr<Builder<Related>> m_query;
/*! The parent model instance. */
const Model m_parent;
/*! The related model instance. */
const Related m_related;
/*! Indicates if the relation is adding constraints. */
static bool constraints;
};
class OneRelation
{};
class ManyRelation
{};
template<class Model, class Related>
bool Relation<Model, Related>::constraints = true;
template<class Model, class Related>
Relation<Model, Related>::Relation(std::unique_ptr<Builder<Related>> &&query,
const Model &parent)
: m_query(std::move(query))
, m_parent(parent)
, m_related(m_query->getModel())
{}
template<class Model, class Related>
std::unique_ptr<Relation<Model, Related>>
Relation<Model, Related>::noConstraints(
const std::function<std::unique_ptr<Relation<Model, Related>>()> &callback)
{
const auto previous = constraints;
constraints = false;
auto relation = std::invoke(callback);
constraints = previous;
return relation;
}
} // namespace Orm::Tiny::Relations
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // RELATION_H

View File

@@ -0,0 +1,14 @@
#include "tinybuilder.h"
#include "orm/tiny/basemodel.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
} // namespace Orm::Tiny
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif

493
src/orm/tiny/tinybuilder.h Normal file
View File

@@ -0,0 +1,493 @@
#ifndef TINYBUILDER_H
#define TINYBUILDER_H
#include <QDebug>
#include <QtSql/QSqlRecord>
#include <range/v3/algorithm/contains.hpp>
#include <range/v3/algorithm/move.hpp>
#include <range/v3/iterator/insert_iterators.hpp>
#include "orm/databaseconnection.h"
#include "orm/query/querybuilder.h"
#include "orm/tiny/relations/relation.h"
#include "orm/tiny/relations/belongsto.h"
#include "orm/tiny/relations/hasmany.h"
#ifdef MANGO_COMMON_NAMESPACE
namespace MANGO_COMMON_NAMESPACE
{
#endif
namespace Orm::Tiny
{
template<typename Model, typename ...AllRelations>
class BaseModel;
namespace Relations
{
template<class Model, class Related>
class Relation;
}
template<typename Model>
class Builder
{
public:
Builder(const QSharedPointer<QueryBuilder> query, Model &model);
/*! Execute the query as a "select" statement. */
QVector<Model> get(const QStringList &columns = {"*"});
/* BuildsQueries */
// TODO BuildsQueries contains duplicit methods in TinyBuilder and QueryBuilder, make it by multi inheritance, I discovered now, that TinyBuilder will return different types than QueryBuilder, look eg at first() or get(), but investigate if there are cases, when API is same and use multi inheritance patter for this methods silver
/*! Execute the query and get the first result. */
std::optional<Model> first(const QStringList &columns = {"*"});
/*! Get the hydrated models without eager loading. */
QVector<Model> getModels(const QStringList &columns = {"*"});
/*! Eager load the relationships for the models. */
void eagerLoadRelations(QVector<Model> &models);
/*! Eagerly load the relationship on a set of models. */
template<typename Related>
void eagerLoadRelation(QVector<Model> &models, const WithItem &relationItem);
/*! Get the relation instance for the given relation name. */
template<typename Related>
auto getRelation(const QString &name);
/*! Create a collection of models from plain arrays. */
QVector<Model> hydrate(QSqlQuery result);
/*! Set the relationships that should be eager loaded. */
Builder &with(const QVector<WithItem> &relations);
/*! Set the relationships that should be eager loaded. */
inline Builder &with(const QString &relation)
{ return with({relation}); }
/*! Create a new instance of the model being queried. */
Model newModelInstance(const QVector<AttributeItem> &attributes = {});
/* Proxy methods to the QueryBuilder */
// TODO for all proxy methods can be used parameter pack, check consequences silverqx
/*! Add a basic where clause to the query. */
Builder &where(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition = "and");
/*! Add a "where null" clause to the query. */
Builder &whereNull(const QStringList &columns = {"*"},
const QString &condition = "and", bool nope = false);
/*! Add a "where null" clause to the query. */
Builder &whereNull(const QString &column, const QString &condition = "and",
bool nope = false);
/*! Add an "or where null" clause to the query. */
Builder &orWhereNull(const QStringList &columns = {"*"});
/*! Add an "or where null" clause to the query. */
Builder &orWhereNull(const QString &column);
/*! Add a "where not null" clause to the query. */
Builder &whereNotNull(const QStringList &columns = {"*"},
const QString &condition = "and");
/*! Add a "where not null" clause to the query. */
Builder &whereNotNull(const QString &column, const QString &condition = "and");
/*! Add an "or where not null" clause to the query. */
Builder &orWhereNotNull(const QStringList &columns = {"*"});
/*! Add an "or where not null" clause to the query. */
Builder &orWhereNotNull(const QString &column);
/*! Set the "limit" value of the query. */
Builder &limit(int value);
/*! Alias to set the "limit" value of the query. */
Builder &take(int value);
/*! Set the "offset" value of the query. */
Builder &offset(int value);
/*! Alias to set the "offset" value of the query. */
Builder &skip(int value);
/*! Set the limit and offset for a given page. */
Builder &forPage(int page, int perPage = 30);
/*! Get the model instance being queried. */
inline Model &getModel()
{ return m_model; }
/*! Get the underlying query builder instance. */
inline QueryBuilder &getQuery() const
{ return *m_query; }
protected:
/*! Parse a list of relations into individuals. */
QVector<WithItem> parseWithRelations(const QVector<WithItem> &relations) const;
/*! Create a constraint to select the given columns for the relation. */
WithItem createSelectWithConstraint(const QString &name) const;
/*! Parse the nested relationships in a relation. */
void addNestedWiths(const QString &name, QVector<WithItem> &results) const;
/*! Get the deeply nested relations for a given top-level relation. */
QVector<WithItem>
relationsNestedUnder(const QString &topRelationName) const;
/*! Determine if the relationship is nested. */
bool isNestedUnder(const QString &topRelation, const QString &nestedRelation) const;
/*! The base query builder instance. */
const QSharedPointer<QueryBuilder> m_query;
/*! The model being queried. */
Model m_model;
/*! The relationships that should be eager loaded. */
QVector<WithItem> m_eagerLoad;
};
template<typename Model>
Builder<Model>::Builder(const QSharedPointer<QueryBuilder> query,
Model &model)
: m_query(query)
, m_model(model)
{
m_query->from(m_model.getTable());
}
template<typename Model>
QVector<Model>
Builder<Model>::get(const QStringList &columns)
{
auto models = getModels(columns);
/* If we actually found models we will also eager load any relationships that
have been specified as needing to be eager loaded, which will solve the
n+1 query issue for the developers to avoid running a lot of queries. */
if (models.size() > 0)
/* 'models' are passed down as the reference and relations are set on models
at the end of the call tree, no need to return models. */
eagerLoadRelations(models);
return models;
// Laravel does it this way
// return $builder->getModel()->newCollection($models);
}
template<typename Model>
QVector<Model>
Builder<Model>::getModels(const QStringList &columns)
{
return hydrate(std::get<1>(m_query->get(columns)));
}
template<typename Model>
std::optional<Model>
Builder<Model>::first(const QStringList &columns)
{
const auto models = take(1).get(columns);
if (models.isEmpty())
return std::nullopt;
return models.first();
}
template<typename Model>
void Builder<Model>::eagerLoadRelations(QVector<Model> &models)
{
if (m_eagerLoad.isEmpty())
return;
for (const auto &relation : qAsConst(m_eagerLoad))
/* For nested eager loads we'll skip loading them here and they will be set as an
eager load on the query to retrieve the relation so that they will be eager
loaded on that query, because that is where they get hydrated as models. */
if (!relation.name.contains(QChar('.')))
m_model.eagerLoadRelationVisitor(relation, *this, models);
}
template<typename Model>
template<typename Related>
void Builder<Model>::eagerLoadRelation(QVector<Model> &models,
const WithItem &relationItem)
{
/* First we will "back up" the existing where conditions on the query so we can
add our eager constraints. Then we will merge the wheres that were on the
query back to it in order that any where conditions might be specified. */
auto relation = getRelation<Related>(relationItem.name);
relation->addEagerConstraints(models);
// Add relation contraints defined in a user callback
// std::invoke(relationItem.constraints);
/* Once we have the results, we just match those back up to their parent models
using the relationship instance. Then we just return the finished arrays
of models which have been eagerly hydrated and are readied for return. */
relation->match(relation->initRelation(models, relationItem.name),
relation->getEager(), relationItem.name);
}
template<typename Model>
template<typename Related>
auto Builder<Model>::getRelation(const QString &name)
{
const auto &method = m_model.getRelationMethod(name);
/* We want to run a relationship query without any constrains so that we will
not have to remove these where clauses manually which gets really hacky
and error prone. We don't want constraints because we add eager ones. */
auto relation = Relations::Relation<Model, Related>::noConstraints(
[this, &method]
{
return std::invoke(std::any_cast<RelationType<Model, Related>>(method),
getModel().newInstance());
});
const auto nested = relationsNestedUnder(name);
/* If there are nested relationships set on the query, we will put those onto
the query instances so that they can be handled after this relationship
is loaded. In this way they will all trickle down as they are loaded. */
if (nested.size() > 0)
relation->getQuery().with(nested);
return relation;
}
template<typename Model>
QVector<Model>
Builder<Model>::hydrate(QSqlQuery result)
{
auto instance = newModelInstance();
QVector<Model> models;
while (result.next()) {
// Table row
QVector<AttributeItem> row;
// Populate table row with data from the database
const auto record = result.record();
for (int i = 0; i < record.count(); ++i)
row.append({record.fieldName(i), result.value(i)});
// Create a new model instance from the table row
models.append(m_model.newFromBuilder(row));
}
return models;
}
template<typename Model>
Builder<Model> &
Builder<Model>::with(const QVector<WithItem> &relations)
{
auto eagerLoad = parseWithRelations(relations);
ranges::move(eagerLoad, ranges::back_inserter(m_eagerLoad));
return *this;
}
template<typename Model>
Model Builder<Model>::newModelInstance(const QVector<AttributeItem> &attributes)
{
return m_model.newInstance(attributes)
.setConnection(m_query->getConnection().getName());
}
template<typename Model>
Builder<Model> &
Builder<Model>::where(const QString &column, const QString &comparison,
const QVariant &value, const QString &condition)
{
m_query->where(column, comparison, value, condition);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::whereNull(const QStringList &columns, const QString &condition,
const bool nope)
{
m_query->whereNull(columns, condition, nope);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::whereNull(const QString &column, const QString &condition,
const bool nope)
{
return whereNull(QStringList(column), condition, nope);
}
template<typename Model>
Builder<Model> &
Builder<Model>::orWhereNull(const QStringList &columns)
{
m_query->orWhereNull(columns);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::orWhereNull(const QString &column)
{
return orWhereNull(QStringList(column));
}
template<typename Model>
Builder<Model> &
Builder<Model>::whereNotNull(const QStringList &columns, const QString &condition)
{
m_query->whereNotNull(columns, condition);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::whereNotNull(const QString &column, const QString &condition)
{
return whereNotNull(QStringList(column), condition);
}
template<typename Model>
Builder<Model> &
Builder<Model>::orWhereNotNull(const QStringList &columns)
{
m_query->orWhereNotNull(columns);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::orWhereNotNull(const QString &column)
{
return orWhereNotNull(QStringList(column));
}
template<typename Model>
Builder<Model> &
Builder<Model>::limit(const int value)
{
m_query->limit(value);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::take(const int value)
{
return limit(value);
}
template<typename Model>
Builder<Model> &
Builder<Model>::offset(const int value)
{
m_query->offset(value);
return *this;
}
template<typename Model>
Builder<Model> &
Builder<Model>::skip(const int value)
{
return offset(value);
}
template<typename Model>
Builder<Model> &
Builder<Model>::forPage(const int page, const int perPage)
{
m_query->forPage(page, perPage);
return *this;
}
template<typename Model>
QVector<WithItem>
Builder<Model>::parseWithRelations(const QVector<WithItem> &relations) const
{
QVector<WithItem> results;
for (auto relation : relations) {
const auto emptyConstraints = !relation.constraints;
const auto isSelectConstraint = relation.name.contains(QChar(':'));
/* Select columns constraints are only allowed, when relation.constraints
is nullptr. */
if (isSelectConstraint)
Q_ASSERT(!emptyConstraints);
if (emptyConstraints && isSelectConstraint)
relation = createSelectWithConstraint(relation.name);
/* We need to separate out any nested includes, which allows the developers
to load deep relationships using "dots" without stating each level of
the relationship with its own key in the array of eager-load names. */
addNestedWiths(relation.name, results);
results.append(relation);
}
return results;
}
template<typename Model>
WithItem Builder<Model>::createSelectWithConstraint(const QString &name) const
{
return {
name.split(QChar(':')).first(), [name](/*auto &query*/)
{
// query->select(name.split(QChar(':')).last().split(QChar(',')));
}
};
}
template<typename Model>
void Builder<Model>::addNestedWiths(const QString &name,
QVector<WithItem> &results) const
{
QStringList progress;
/* If the relation has already been set on the result array, we will not set it
again, since that would override any constraints that were already placed
on the relationships. We will only set the ones that are not specified. */
// Prevent container detach
const auto names = name.split(QChar('.'));
for (const auto &segment : names) {
progress << segment;
const auto last = progress.join(QChar('.'));
const auto containsRelation = [&last](const auto &relation)
{
return relation.name == last;
};
const auto contains = ranges::contains(results, true, containsRelation);
// Don't add a relation in the 'name' variable
if (!contains && (last != name))
results.append({last});
}
}
template<typename Model>
QVector<WithItem>
Builder<Model>::relationsNestedUnder(const QString &topRelationName) const
{
QVector<WithItem> nested;
/* We are basically looking for any relationships that are nested deeper than
the given top-level relationship. We will just check for any relations
that start with the given top relations and adds them to our arrays. */
for (const auto &relation : m_eagerLoad)
if (isNestedUnder(topRelationName, relation.name))
nested.append({relation.name.mid(topRelationName.size() + 1),
relation.constraints});
return nested;
}
template<typename Model>
bool Builder<Model>::isNestedUnder(const QString &topRelation,
const QString &nestedRelation) const
{
return nestedRelation.contains(QChar('.'))
&& nestedRelation.startsWith(topRelation + QChar('.'));
}
} // namespace Orm::Tiny
#ifdef MANGO_COMMON_NAMESPACE
} // namespace MANGO_COMMON_NAMESPACE
#endif
#endif // TINYBUILDER_H

14
src/orm/utils/string.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "string.h"
QString Utils::String::toSnake(const QString &string)
{
static const QRegularExpression regExp1 {"(.)([A-Z][a-z]+)"};
static const QRegularExpression regExp2 {"([a-z0-9])([A-Z])"};
QString result = string;
result.replace(regExp1, "\\1_\\2");
result.replace(regExp2, "\\1_\\2");
return result.toLower();
}

13
src/orm/utils/string.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef UTILS_STRING_H
#define UTILS_STRING_H
namespace Utils
{
namespace String
{
/*! Convert a string to snake case. */
QString toSnake(const QString &string);
}
}
#endif // UTILS_STRING_H

29
src/orm/utils/type.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef UTILS_TYPE_H
#define UTILS_TYPE_H
#include <QRegularExpression>
namespace Utils
{
namespace Type
{
/*! Class name without a namespace and template parameters. */
template<typename Type>
inline QString classPureBasename()
{
QRegularExpression re(
QStringLiteral("(?:(?<=^struct )\\w+|(?<=^class )\\w+|(?<=::)\\w+)"
"(?=<.*>| |$)"));
const auto match = re.match(typeid (Type).name());
// This should never happen, but who knows 🤔
Q_ASSERT_X(match.hasMatch(),
"regex match", "Can not get class base name in getForeignKey().");
return match.captured();
}
}
}
#endif // UTILS_TYPE_H

51
src/src.pri Normal file
View File

@@ -0,0 +1,51 @@
INCLUDEPATH += $$PWD
SOURCES += \
$$PWD/orm/databaseconnection.cpp \
$$PWD/orm/entitymanager.cpp \
$$PWD/orm/expression.cpp \
$$PWD/orm/grammar.cpp \
$$PWD/orm/logquery.cpp \
$$PWD/orm/ormtypes.cpp \
$$PWD/orm/query/joinclause.cpp \
$$PWD/orm/query/querybuilder.cpp \
$$PWD/orm/repositoryfactory.cpp \
$$PWD/orm/tiny/basemodel.cpp \
$$PWD/orm/tiny/concerns/hasattributes.cpp \
$$PWD/orm/tiny/relations/belongsto.cpp \
$$PWD/orm/tiny/relations/hasmany.cpp \
$$PWD/orm/tiny/relations/hasone.cpp \
$$PWD/orm/tiny/relations/hasoneormany.cpp \
$$PWD/orm/tiny/relations/relation.cpp \
$$PWD/orm/tiny/tinybuilder.cpp\
$$PWD/orm/utils/string.cpp \
$$PWD/testorm.cpp \
$$PWD/torrent.cpp \
$$PWD/torrentpeer.cpp \
$$PWD/torrentpreviewablefile.cpp
HEADERS += \
$$PWD/orm/databaseconnection.h \
$$PWD/orm/entitymanager.h \
$$PWD/orm/expression.h \
$$PWD/orm/grammar.h \
$$PWD/orm/logquery.h \
$$PWD/orm/ormerror.h \
$$PWD/orm/ormtypes.h \
$$PWD/orm/query/joinclause.h \
$$PWD/orm/query/querybuilder.h \
$$PWD/orm/repositoryfactory.h \
$$PWD/orm/tiny/basemodel.h \
$$PWD/orm/tiny/concerns/hasattributes.h \
$$PWD/orm/tiny/relations/belongsto.h \
$$PWD/orm/tiny/relations/hasmany.h \
$$PWD/orm/tiny/relations/hasone.h \
$$PWD/orm/tiny/relations/hasoneormany.h \
$$PWD/orm/tiny/relations/relation.h \
$$PWD/orm/tiny/tinybuilder.h \
$$PWD/orm/utils/string.h \
$$PWD/orm/utils/type.h \
$$PWD/testorm.h \
$$PWD/torrent.h \
$$PWD/torrentpeer.h \
$$PWD/torrentpreviewablefile.h

415
src/testorm.cpp Normal file
View File

@@ -0,0 +1,415 @@
#include "testorm.h"
#include <QDebug>
#include <iostream>
#include "torrent.h"
#include "torrentpreviewablefile.h"
#include "torrentpeer.h"
#include <orm/ormtypes.h>
#include <range/v3/all.hpp>
using namespace ranges;
// TODO also investigate &&, when to use, I also see usage in for range loops for values silverqx
void TestOrm::run()
{
// anotherTests();
testTinyOrm();
// testQueryBuilder();
}
void TestOrm::anotherTests()
{
// printf("Function name: %s\n", __FUNCTION__);
// printf("Decorated function name: %s\n", __FUNCDNAME__);
// printf("Function signature: %s\n", __FUNCSIG__);
qt_noop();
}
void TestOrm::testTinyOrm()
{
TorrentPreviewableFile a;
auto files = a.query()->where("torrent_id", "=", 2).get();
//// auto files = a.query()->where("torrent_id", "=", 261).get();
//// auto torrent1 = files.first().getRelation<Torrent, QVector>("torrent");
auto torrent1 = files.first().getRelation<Torrent, Orm::One>("torrent");
// qDebug() << torrent1->getAttribute("name");
auto peer1 = torrent1->getRelation<TorrentPeer, Orm::One>("torrentPeer");
//// auto torrent2 = files.first().getRelationValue<Torrent>("torrent");
// auto torrent3 = files.first().getRelationValue<Torrent, Orm::One>("torrent");
// auto torrent3 = a.getRelationValue<Torrent, Orm::One>("torrent");
//// qDebug() << torrent2;
//// qDebug() << torrent3.has_value();
qt_noop();
// qMetaTypeId<KeyType>();
// QVector<quint64> vec {12, 6, 8, 2, 7, 8, 8};
// QVector<QVariant> var(vec.begin(), vec.end());
// std::sort(vec.begin(), vec.end());
// std::sort(var.begin(), var.end(), [](QVariant a, QVariant b) {
// return a.value<KeyType>() < b.value<KeyType>();
// });
//// std::unique(var.begin(), var.end());
// auto last = std::unique(vec.begin(), vec.end());
// vec.erase(last, vec.end());
// qDebug() << vec;
// auto last1 = std::unique(var.begin(), var.end(), [](QVariant a, QVariant b) {
// return a.value<KeyType>() == b.value<KeyType>();
// });
// var.erase(last1, var.end());
// qDebug() << var;
// using namespace ranges;
// vec |= actions::sort | actions::unique;
// qDebug() << vec;
// using namespace ranges;
//// var |= actions::sort([](QVariant a, QVariant b) {
//// return a.value<KeyType>() == b.value<KeyType>();
//// })/* | actions::unique*/;
// auto nn = std::move(var) | actions::sort(less {}, &QVariant::value<KeyType>) | actions::unique;
//// var |= actions::sort(less {}, &QVariant::value<KeyType>) | actions::unique;
// qDebug() << nn;
// qt_noop();
// Torrent b;
// std::any x = b;
// qDebug() << x.type().name();
// std::any a = 1;
// qDebug() << a.type().name() << ": " << std::any_cast<int>(a) << '\n';
// auto aa = std::any_cast<Torrent>(x);
// std::variant<int, QString> v {10};
// auto &x = std::get<int>(v);
// x = 11;
// auto y = std::get<int>(v);
// qDebug() << x;
// qDebug() << y;
// QVector<QVariantMap> values;
// auto nums = views::iota(1, 1000);
// for (const auto &i : nums)
// values.append({{"torrent_id", 7}, {"file_index", 0},
// {"filepath", QStringLiteral("test7_file%1.mkv").arg(i)},
// {"size", i}, {"progress", 50}});
// m_em.queryBuilder()->from("torrent_previewable_files")
// .insert(values);
// qt_noop();
Torrent b;
auto torrents = b.query()->where("id", "=", 2).get();
auto peer = torrents.first().getRelation<TorrentPeer, Orm::One>("torrentPeer");
////// auto peer = torrents.first().getRelationValue<TorrentPeer, Orm::One>("torrentPeer");
////// auto peer = b.getRelationValue<TorrentPeer, Orm::One>("torrentPeer");
//// qDebug() << "peers :" << !!peer;
// auto files = torrents.first().getRelation<TorrentPreviewableFile>("torrentFiles");
////// auto files = b.getRelationValue<TorrentPreviewableFile>("torrentFiles");
qt_noop();
/* Model::with() */
// {
// auto torrents = Torrent().with("torrentPeer")->get();
// auto peer = torrents[1].getRelation<TorrentPeer, Orm::One>("torrentPeer");
// qDebug() << peer->getAttribute("id");
// qt_noop();
// }
// {
// auto torrents = Torrent().with("torrentFiles")->get();
// auto files = torrents[0].getRelation<TorrentPreviewableFile>("torrentFiles");
// for (const auto &file : files)
// qDebug() << file.getAttribute("filepath");
// qt_noop();
// }
qt_noop();
}
void TestOrm::testQueryBuilder()
{
// /* RANGES range-v3 lib */
// auto v = std::vector<std::string> {"apple", "banana", "kiwi"};
// for (auto&& [first, second] : v | ranges::views::enumerate)
// qDebug() << first << ", " << QString::fromStdString(second);
// qt_noop();
//// std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };
// QVector<int> numbers { 1, 2, 3, 4, 5, 6 };
// auto results = numbers
// | ranges::views::filter([](int n){ return n % 2 == 0; })
// | ranges::views::transform([](int n){ return n * 2; });
// for (const auto &v : results)
// qDebug() << v;
// qt_noop();
// /* QT PROPERTY system */
// MyClass *myinstance = new MyClass;
// auto r1 = myinstance->setProperty("name", 10);
// auto v1 = myinstance->property("name");
// auto dp = myinstance->dynamicPropertyNames();
// for (auto &prop : dp)
// qDebug() << prop;
// qt_noop();
/* distinct, limit, offset and ordering */
// auto a = m_em.queryBuilder()->from("torrents")
//// .distinct()
//// .where("progress", ">", 100)
//// .limit(5)
//// .offset(2)
//// .forPage(2, 5)
// .orderBy("name")
// .reorder()
// .orderBy("id", "desc")
//// .orderByDesc("size")
// .get();
// qDebug() << "FIRST :" << a.executedQuery();
// while (a.next()) {
// qDebug() << "id :" << a.value("id") << "; name :" << a.value("name");
// }
// qt_noop();
// auto [ok, b] = m_em.queryBuilder()->from("torrents").get({"id, name"});
// qDebug() << "SECOND :" << b.executedQuery();
// while (b.next()) {
// qDebug() << "id :" << b.value("id") << "; name :" << b.value("name");
// }
// qt_noop();
/* WHERE */
// auto [ok, c] = m_em.queryBuilder()->from("torrents")
// .where("name", "=", "Internat - S1", "and")
// .get({"id", "name"});
// qDebug() << "THIRD :" << c.executedQuery();
// while (c.next()) {
// qDebug() << "id :" << c.value("id") << "; name :" << c.value("name");
// }
// qt_noop();
/* also nested where */
// auto [ok, d] = m_em.queryBuilder()->from("torrents")
// .where("name", "=", "aliens", "and")
//// .where("id", "=", 1, "and")
//// .where("id", "=", 262, "and")
//// .where("id", "=", 261, "or")
// .where([](auto &query)
// {
//// query.where("id", "=", 261, "or");
// query.where("id", "=", 258, "or");
//// query.where("name", "=", "Most II", "or");
// query.where([](auto &query)
// {
// query.where("id", "=", 5);
// query.where("name", "=", "Transformers 2");
// }, "or");
// }, "or")
// .get({"id, name"});
// qDebug() << "FOURTH :" << d.executedQuery();
// while (d.next()) {
// qDebug() << "id :" << d.value("id") << "; name :" << d.value("name");
// }
// qt_noop();
// whereIn
// auto [ok_c1, c1] = m_em.queryBuilder()->from("torrents")
// .where("name", "=", "Internat - S1", "and")
// .get({"id", "name"});
// qDebug() << "whereIn :" << c1.executedQuery();
// while (c1.next()) {
// qDebug() << "id :" << c1.value("id") << "; name :" << c1.value("name");
// }
// qt_noop();
/* JOINs */
// auto e = m_em.queryBuilder()->from("torrents")
// .rightJoin("torrent_previewable_files", "torrents.id", "=", "torrent_id")
// .where("torrents.id", "=", 256)
// .get({"torrents.id", "name", "file_index", "filepath"});
// qDebug() << "FIFTH :" << e.executedQuery();
// while (e.next()) {
// qDebug() << "id :" << e.value("id") << "; name :" << e.value("name")
// << "file_index :" << e.value("file_index")
// << "filepath :" << e.value("filepath");
// }
// auto e = m_em.queryBuilder()->from("torrents")
// .join("torrent_previewable_files", [](auto &join)
// {
// join.on("torrents.id", "=", "torrent_id")
// .where("torrents.progress", "<", 20);
// })
//// .where("torrents.id", "=", 256)
// .get({"torrents.id", "name", "file_index", "filepath"});
// qDebug() << "SIXTH :" << e.executedQuery();
// while (e.next()) {
// qDebug() << "id :" << e.value("id") << "; name :" << e.value("name")
// << "file_index :" << e.value("file_index")
// << "filepath :" << e.value("filepath");
// }
// qt_noop();
/* first and find */
// auto f = m_em.queryBuilder()->from("torrents")
// .where("torrents.id", "=", 256)
//// .first({"id", "name"});
// .value("name");
//// qDebug() << "SEVENTH :" << f.executedQuery();
//// qDebug() << "id :" << f.value("id") << "; name :" << f.value("name");
// qDebug() << "name:" << f;
// qt_noop();
// auto [ok, g] = m_em.queryBuilder()->from("torrents")
// .find(256, {"id", "name"});
// qDebug() << "EIGTH :" << g.executedQuery();
// qDebug() << "id :" << g.value("id") << "; name :" << g.value("name");
// qt_noop();
/* GROUP BY and HAVING */
// auto h = m_em.queryBuilder()->from("torrents")
// .groupBy({"status"})
//// .having("status", ">", 10)
//// .having("status", ">", "Paused")
// .having("status", ">", static_cast<int>(TorrentStatus::Paused))
// .get({"id", "name", "status"});
// qDebug() << "NINETH :" << h.executedQuery();
// while (h.next()) {
// qDebug() << "id :" << h.value("id") << "; name :" << h.value("name")
// << "status :" << h.value("status");
// }
/* INSERTs */
// auto id_i = m_em.queryBuilder()->table("torrents").insertGetId(
// {{"name", "first"}, {"progress", 300}, {"eta", 8000000}, {"size", 2048},
// {"seeds", 0}, {"total_seeds", 0}, {"leechers", 0}, {"total_leechers", 0},
// {"remaining", 1024},
// {"added_on", QDateTime().currentDateTime().toString(Qt::ISODate)},
// {"hash", "xxxx61defa3daecacfde5bde0214c4a439351d4d"},
// {"status", static_cast<int>(TorrentStatus::Stalled)},
// {"savepath", "D:/downloads/uTorrent/downloads/_dev1/_videos"}});
// qDebug() << "TENTH";
// qDebug() << "last id :" << id_i;
// qt_noop();
// const auto id_i = 278;
// auto [ok_j, j] = m_em.queryBuilder()->table("torrent_previewable_files").insert({
// {{"torrent_id", id_i}, {"file_index", 0}, {"filepath", "abc.mkv"}, {"size", 2048},
// {"progress", 10}},
// {{"torrent_id", id_i}, {"file_index", 1}, {"filepath", "xyz.mkv"}, {"size", 1024},
// {"progress", 15}}});
// qDebug() << "ELEVEN :" << j->executedQuery();
// if (ok_j) {
// qDebug() << "last id :" << j->lastInsertId()
// << "; affected rows :" << j->numRowsAffected();
// }
// qt_noop();
// auto [ok_k, k] = m_em.queryBuilder()->table("torrent_previewable_files").insert({
// {"torrent_id", id_i}, {"file_index", 2}, {"filepath", "qrs.mkv"}, {"size", 3074},
// {"progress", 20}});
// qDebug() << "TWELVE :" << k->executedQuery();
// if (ok_k) {
// qDebug() << "last id :" << k->lastInsertId()
// << "; affected rows :" << k->numRowsAffected();
// }
// qt_noop();
// const auto id_l = 278;
// auto [ok_l, l] = m_em.queryBuilder()->table("torrent_previewable_files").insertOrIgnore({
// {{"torrent_id", id_l}, {"file_index", 2}, {"filepath", "qrs.mkv"}, {"size", 3074},
// {"progress", 20}},
// {{"torrent_id", id_l}, {"file_index", 3}, {"filepath", "ghi.mkv"}, {"size", 3074},
// {"progress", 20}},
// {{"torrent_id", id_l}, {"file_index", 4}, {"filepath", "def.mkv"}, {"size", 3074},
// {"progress", 20}},
// });
// qDebug() << "TWELVE :" << l->executedQuery();
// if (ok_l) {
// qDebug() << "last id :" << l->lastInsertId() // undefined behavior
// << "; affected rows :" << l->numRowsAffected();
// }
// qt_noop();
/* UPDATEs */
// auto [affected_m, m] = m_em.queryBuilder()->table("torrents")
// .where("id", "=", 277)
// .update({{"name", "first1"}, {"progress", 350}});
// qDebug() << "THIRTEEN :" << m.executedQuery();
// qDebug() << "affected rows :" << affected_m;
// qt_noop();
// auto [affected_n, n] = m_em.queryBuilder()->table("torrents")
// .join("torrent_previewable_files", "torrents.id", "=",
// "torrent_previewable_files.torrent_id")
// .where("torrents.id", "=", 277)
// .where("torrent_previewable_files.id", "=", 100)
// .update({{"name", "first4"}, {"torrents.progress", 354},
// {"torrent_previewable_files.progress", 15}});
// qDebug() << "FOURTEEN :" << n.executedQuery();
// qDebug() << "affected rows :" << affected_n;
// qt_noop();
/* EXPRESSIONs */
// Expression aa("first1");
// QVariant x;
// x.setValue(aa);
// QVariant x {Expression("first1")};
// QVariant x = Expression("first1");
// QVariant x = QVariant::fromValue(Expression("first1"));
// qDebug() << "text :" << x;
// qDebug() << "type :" << x.type();
// qDebug() << "typeName :" << x.typeName();
// qDebug() << "userType :" << x.userType();
// qt_noop();
// auto [affected_o, o] = m_em.queryBuilder()->table("torrents")
// .where("id", "=", 277)
// .update({{"name", QVariant::fromValue(Expression("first"))}, {"progress", 350}});
//// .update({{"name", x}, {"progress", 350}});
// qDebug() << "FIFTEEN :" << o.executedQuery();
// qDebug() << "affected rows :" << affected_o;
// qt_noop();
/* INCREMENT / DECREMENT */
// auto [affected_p, p] = m_em.queryBuilder()->table("torrents")
// .whereEq("id", 277)
//// .increment("progress", 1);
// .decrement("progress", 1, {
// {"seeds", 6}
// });
// qDebug() << "SIXTEEN :" << p.executedQuery();
// qDebug() << "affected rows :" << affected_p;
// qt_noop();
/* DELETEs */
// auto [affected_q, q] = m_em.queryBuilder()->table("torrent_previewable_files")
// .whereEq("id", 107)
// .remove();
// qDebug() << "SEVENTEEN :" << q.executedQuery();
// qDebug() << "affected rows :" << affected_q;
// qt_noop();
// auto [affected_r, r] = m_em.queryBuilder()->table("torrents")
// .join("torrent_previewable_files", "torrents.id", "=",
// "torrent_previewable_files.torrent_id")
// .whereEq("torrents.id", 277)
// .whereEq("torrent_previewable_files.id", 106)
// .remove();
// qDebug() << "EIGHTEEN :" << r.executedQuery();
// qDebug() << "affected rows :" << affected_r;
// qt_noop();
/* TRUNCATE */
// auto [ok_s, s] = m_em.queryBuilder()->table("xxx")
// .truncate();
// qDebug() << "NINETEEN :" << s.executedQuery();
// ok_s ? qDebug() << "truncate was successful"
// : qDebug() << "truncate was unsuccessful";
// qt_noop();
}

51
src/testorm.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef TESTORM_H
#define TESTORM_H
#include "orm/entitymanager.h"
class TestOrm
{
public:
TestOrm() = default;
using KeyType = quint64;
void run();
private:
void anotherTests();
void testTinyOrm();
void testQueryBuilder();
Orm::EntityManager m_em;
};
//class MyClass : public QObject
//{
// Q_OBJECT
//// Q_PROPERTY(Priority priority READ priority WRITE setPriority)
// Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
//public:
// MyClass(QObject *parent = nullptr) {};
// ~MyClass() = default;
// enum Priority { High, Low, VeryHigh, VeryLow };
// Q_ENUM(Priority)
// void setPriority(Priority priority)
// {
// m_priority = priority;
// emit priorityChanged(priority);
// }
// Priority priority() const
// { return m_priority; }
//signals:
// void priorityChanged(Priority);
//private:
// Priority m_priority;
//};
#endif // TESTORM_H

14
src/torrent.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "torrent.h"
Torrent::Torrent(const QVector<Orm::AttributeItem> &attributes)
: BaseModel(attributes)
{}
QDebug operator<<(QDebug debug, const Torrent &c)
{
QDebugStateSaver saver(debug);
debug.nospace() << c.getAttribute("id") << ", "
<< c.getAttribute("name");
return debug;
}

68
src/torrent.h Normal file
View File

@@ -0,0 +1,68 @@
#ifndef TORRENTS_H
#define TORRENTS_H
#include "orm/tiny/basemodel.h"
#include "torrentpeer.h"
#include "torrentpreviewablefile.h"
/* This class serves as a showcase, so all possible features are defined / used. */
class Torrent final : public Orm::Tiny::BaseModel<Torrent, TorrentPreviewableFile, TorrentPeer>
{
public:
friend class BaseModel;
/*! The "type" of the primary key ID. */
using KeyType = quint64;
Torrent(const QVector<Orm::AttributeItem> &attributes = {});
std::unique_ptr<
Orm::Tiny::Relations::Relation<Torrent, TorrentPreviewableFile>>
torrentFiles()
{
return hasMany<TorrentPreviewableFile>();
// return hasMany<TorrentPreviewableFile>("torrent_id", "id");
}
std::unique_ptr<
Orm::Tiny::Relations::Relation<Torrent, TorrentPeer>>
torrentPeer()
{
return hasOne<TorrentPeer>();
// return hasOne<TorrentPeer>("torrent_id", "id");
}
private:
void eagerVisitor(const QString &relation)
{
if (relation == "torrentFiles")
eagerVisited<TorrentPreviewableFile>();
else if (relation == "torrentPeer")
eagerVisited<TorrentPeer>();
}
/*! The table associated with the model. */
QString u_table {"torrents"};
/*! Map of relation names to methods. */
QHash<QString, std::any> u_relations {
{"torrentFiles", &Torrent::torrentFiles},
{"torrentPeer", &Torrent::torrentPeer},
};
/*! The relations to eager load on every query. */
QVector<Orm::WithItem> u_with {
// {"torrentFiles"},
{"torrentPeer"},
};
/*! The connection name for the model. */
// QString u_connection {"crystal"};
};
// TODO finish this, move to base class and test eg in qvector, qhash, etc silverqx
QDebug operator<<(QDebug debug, const Torrent &c);
#endif // TORRENTS_H

5
src/torrentpeer.cpp Normal file
View File

@@ -0,0 +1,5 @@
#include "torrentpeer.h"
TorrentPeer::TorrentPeer(const QVector<Orm::AttributeItem> &attributes)
: BaseModel(attributes)
{}

43
src/torrentpeer.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef TORRENTPEERS_H
#define TORRENTPEERS_H
#include "orm/tiny/basemodel.h"
#include "torrent.h"
class TorrentPeer final : public Orm::Tiny::BaseModel<TorrentPeer, Torrent>
{
public:
friend class BaseModel;
TorrentPeer(const QVector<Orm::AttributeItem> &attributes = {});
std::unique_ptr<
Orm::Tiny::Relations::Relation<TorrentPeer, Torrent>>
torrent()
{
return belongsTo<Torrent>();
}
private:
void eagerVisitor(const QString &relation)
{
if (relation == "torrent")
eagerVisited<Torrent>();
}
/*! The table associated with the model. */
QString u_table {"torrent_peers"};
/*! Map of relation names to methods. */
QHash<QString, std::any> u_relations {
{"torrent", &TorrentPeer::torrent},
};
/*! The relations to eager load on every query. */
QVector<Orm::WithItem> u_with {
// {"torrent"},
};
};
#endif // TORRENTPEERS_H

View File

@@ -0,0 +1,5 @@
#include "torrentpreviewablefile.h"
TorrentPreviewableFile::TorrentPreviewableFile(const QVector<Orm::AttributeItem> &attributes)
: BaseModel(attributes)
{}

View File

@@ -0,0 +1,49 @@
#ifndef TORRENTPREVIEWABLEFILES_H
#define TORRENTPREVIEWABLEFILES_H
#include <QDebug>
#include "torrent.h"
#include "orm/tiny/basemodel.h"
#include "orm/tiny/relations/belongsto.h"
class TorrentPreviewableFile final : public Orm::Tiny::BaseModel<TorrentPreviewableFile, Torrent>
{
public:
friend class BaseModel;
TorrentPreviewableFile(const QVector<Orm::AttributeItem> &attributes = {});
/*! Get the torrent record associated with the user. */
std::unique_ptr<
Orm::Tiny::Relations::Relation<TorrentPreviewableFile, Torrent>>
torrent()
{
return belongsTo<Torrent>();
}
private:
void eagerVisitor(const QString &relation)
{
if (relation == "torrent")
eagerVisited<Torrent>();
}
/*! The table associated with the model. */
QString u_table {"torrent_previewable_files"};
/*! Map of relation names to methods. */
QHash<QString, std::any> u_relations {
{"torrent", &TorrentPreviewableFile::torrent},
};
/*! The relations to eager load on every query. */
QVector<Orm::WithItem> u_with {
// TODO detect (best at compile time) circular eager relation problem, exception during this problem is stackoverflow in QRegularExpression silverqx
{"torrent"},
// WARNING check behavior (in Eloquent too), when relation exactly like below and how it intere when Torrent class has enabled the same relation TorrentPeer, so TorrentPeer is defined like nested in TorrentPreviewableFile "torrent.torrentPeer" and like normal in Torrent "torrentPeer" silverqx
{"torrent.torrentPeer"},
};
};
#endif // TORRENTPREVIEWABLEFILES_H