mirror of
https://github.com/silverqx/TinyORM.git
synced 2025-12-20 09:59:53 -06:00
initial commit
This commit is contained in:
12
.editorconfig
Normal file
12
.editorconfig
Normal 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
7
.gitattributes
vendored
Normal 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
73
.gitignore
vendored
Normal 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
21
LICENSE
Normal 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.
|
||||
3
README.md
Normal file
3
README.md
Normal 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
115
TinyOrm.pro
Normal 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
18
main.cpp
Normal 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
46
pch.h
Normal 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
|
||||
358
src/orm/databaseconnection.cpp
Normal file
358
src/orm/databaseconnection.cpp
Normal 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
|
||||
101
src/orm/databaseconnection.h
Normal file
101
src/orm/databaseconnection.h
Normal 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
88
src/orm/entitymanager.cpp
Normal 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
52
src/orm/entitymanager.h
Normal 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
22
src/orm/expression.cpp
Normal 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
42
src/orm/expression.h
Normal 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
535
src/orm/grammar.cpp
Normal 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
140
src/orm/grammar.h
Normal 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
35
src/orm/logquery.cpp
Normal 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
34
src/orm/logquery.h
Normal 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
28
src/orm/ormerror.h
Normal 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
27
src/orm/ormtypes.cpp
Normal 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
160
src/orm/ormtypes.h
Normal 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
|
||||
26
src/orm/query/joinclause.cpp
Normal file
26
src/orm/query/joinclause.cpp
Normal 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
|
||||
43
src/orm/query/joinclause.h
Normal file
43
src/orm/query/joinclause.h
Normal 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
|
||||
581
src/orm/query/querybuilder.cpp
Normal file
581
src/orm/query/querybuilder.cpp
Normal 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
|
||||
340
src/orm/query/querybuilder.h
Normal file
340
src/orm/query/querybuilder.h
Normal 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
|
||||
17
src/orm/repositoryfactory.cpp
Normal file
17
src/orm/repositoryfactory.cpp
Normal 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
|
||||
54
src/orm/repositoryfactory.h
Normal file
54
src/orm/repositoryfactory.h
Normal 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
|
||||
12
src/orm/tiny/basemodel.cpp
Normal file
12
src/orm/tiny/basemodel.cpp
Normal 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
781
src/orm/tiny/basemodel.h
Normal 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> ⊧
|
||||
};
|
||||
/*! 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
|
||||
12
src/orm/tiny/concerns/hasattributes.cpp
Normal file
12
src/orm/tiny/concerns/hasattributes.cpp
Normal 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
|
||||
61
src/orm/tiny/concerns/hasattributes.h
Normal file
61
src/orm/tiny/concerns/hasattributes.h
Normal 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
|
||||
12
src/orm/tiny/relations/belongsto.cpp
Normal file
12
src/orm/tiny/relations/belongsto.cpp
Normal 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
|
||||
224
src/orm/tiny/relations/belongsto.h
Normal file
224
src/orm/tiny/relations/belongsto.h
Normal 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
|
||||
12
src/orm/tiny/relations/hasmany.cpp
Normal file
12
src/orm/tiny/relations/hasmany.cpp
Normal 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
|
||||
91
src/orm/tiny/relations/hasmany.h
Normal file
91
src/orm/tiny/relations/hasmany.h
Normal 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
|
||||
12
src/orm/tiny/relations/hasone.cpp
Normal file
12
src/orm/tiny/relations/hasone.cpp
Normal 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
|
||||
88
src/orm/tiny/relations/hasone.h
Normal file
88
src/orm/tiny/relations/hasone.h
Normal 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
|
||||
12
src/orm/tiny/relations/hasoneormany.cpp
Normal file
12
src/orm/tiny/relations/hasoneormany.cpp
Normal 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
|
||||
157
src/orm/tiny/relations/hasoneormany.h
Normal file
157
src/orm/tiny/relations/hasoneormany.h
Normal 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
|
||||
12
src/orm/tiny/relations/relation.cpp
Normal file
12
src/orm/tiny/relations/relation.cpp
Normal 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
|
||||
109
src/orm/tiny/relations/relation.h
Normal file
109
src/orm/tiny/relations/relation.h
Normal 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
|
||||
14
src/orm/tiny/tinybuilder.cpp
Normal file
14
src/orm/tiny/tinybuilder.cpp
Normal 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
493
src/orm/tiny/tinybuilder.h
Normal 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
14
src/orm/utils/string.cpp
Normal 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
13
src/orm/utils/string.h
Normal 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
29
src/orm/utils/type.h
Normal 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
51
src/src.pri
Normal 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
415
src/testorm.cpp
Normal 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
51
src/testorm.h
Normal 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
14
src/torrent.cpp
Normal 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
68
src/torrent.h
Normal 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
5
src/torrentpeer.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "torrentpeer.h"
|
||||
|
||||
TorrentPeer::TorrentPeer(const QVector<Orm::AttributeItem> &attributes)
|
||||
: BaseModel(attributes)
|
||||
{}
|
||||
43
src/torrentpeer.h
Normal file
43
src/torrentpeer.h
Normal 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
|
||||
5
src/torrentpreviewablefile.cpp
Normal file
5
src/torrentpreviewablefile.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "torrentpreviewablefile.h"
|
||||
|
||||
TorrentPreviewableFile::TorrentPreviewableFile(const QVector<Orm::AttributeItem> &attributes)
|
||||
: BaseModel(attributes)
|
||||
{}
|
||||
49
src/torrentpreviewablefile.h
Normal file
49
src/torrentpreviewablefile.h
Normal 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
|
||||
Reference in New Issue
Block a user