mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-02-11 04:49:15 -06:00
432 lines
13 KiB
Plaintext
432 lines
13 KiB
Plaintext
Todos to check in TinyOrm:
|
|
-------------------------
|
|
|
|
- QueryBuilder::insertGetId() allows insert with empty attributes, also Model::performInsert()
|
|
when incrementing == true, but all other insert methods don't, it's big inconsistency, unify it
|
|
|
|
|
|
Documentation TinyOrm Todos:
|
|
----------------------------
|
|
|
|
- how to refer NULL in docs, for now I leave it NULL
|
|
|
|
|
|
TODOs which look bad in code:
|
|
-----------------------------
|
|
|
|
- future add onDelete (and similar) callback feature
|
|
|
|
/*! Delete records from the database. */
|
|
void deleteModel()
|
|
{
|
|
// TODO future add onDelete (and similar) callback feature silverqx
|
|
// if (isset($this->onDelete)) {
|
|
// return call_user_func($this->onDelete, $this);
|
|
// }
|
|
|
|
return toBase().deleteRow();
|
|
}
|
|
|
|
|
|
Todo categories:
|
|
----------------
|
|
|
|
- api different : different api than Laravel's Eloquent
|
|
- docs : document code or update markdown documentation
|
|
- duplicate : duplicate code
|
|
- future : task which has lower priority, because still much to do
|
|
- multidriver : task related to adding support for another drivers PostgreSQL, SQLite and SQL Server
|
|
- now : do it before commit
|
|
- next : next thing in the row to do after commit
|
|
- perf : performance
|
|
- postgres : specific to PostgreSQL server
|
|
- primarykey dilema : different types for primary keys
|
|
- production : check before deploy to production
|
|
- security : self explaining
|
|
- test : tasks in auto tests
|
|
- types : juggling with c++ types
|
|
|
|
|
|
IsModel:
|
|
--------
|
|
|
|
/*! Helper class to simply check whether a model is model, eg by std::is_base_of<>. */
|
|
struct IsModel
|
|
{};
|
|
|
|
class BaseModel : public IsModel
|
|
|
|
|
|
Relation store:
|
|
---------------
|
|
|
|
template<typename Model, typename ...AllRelations>
|
|
template<typename Related>
|
|
void BaseModel<Model, AllRelations...>::relationVisited()
|
|
{
|
|
if (dynamic_cast<PushStore *>(m_relationStore) == nullptr)
|
|
eagerVisited<Related>();
|
|
else
|
|
pushVisited<Related>();
|
|
}
|
|
|
|
|
|
Fuckin relationVisited() in u_relations data member:
|
|
-----------------------------------------------------
|
|
|
|
std::invoke(model().u_relations.find(relation.name).value().visitor);
|
|
|
|
struct RelationMethods
|
|
{
|
|
std::any method;
|
|
// This only works for eager load, but doesn't for push silverqx
|
|
std::function<void()> visitor;
|
|
// std::any visitor;
|
|
};
|
|
|
|
|
|
DatabaseConnection config:
|
|
--------------------------
|
|
|
|
QHash<QString, QVariant> config {
|
|
// {"driver", "mysql"},
|
|
// {"url", qEnvironmentVariable("DATABASE_URL")},
|
|
{"host", qEnvironmentVariable("DB_HOST", "127.0.0.1")},
|
|
{"port", qEnvironmentVariable("DB_PORT", "3306")},
|
|
{"database", qEnvironmentVariable("DB_DATABASE", "")},
|
|
{"username", qEnvironmentVariable("DB_USERNAME", "root")},
|
|
{"password", qEnvironmentVariable("DB_PASSWORD", "")},
|
|
// {"unix_socket", qEnvironmentVariable("DB_SOCKET", "")},
|
|
{"charset", qEnvironmentVariable("DB_CHARSET", "utf8mb4")},
|
|
{"collation", qEnvironmentVariable("DB_COLLATION", "utf8mb4_unicode_ci")},
|
|
// {"prefix", ""},
|
|
// {"prefix_indexes", true},
|
|
{"strict", true},
|
|
// {"engine", {}},
|
|
{"options", ""},
|
|
};
|
|
|
|
|
|
DatabaseConnection debug code:
|
|
------------------------------
|
|
|
|
{
|
|
auto [ok, query] = select("select @@session.time_zone, @@global.time_zone");
|
|
while(query.next()) {
|
|
qDebug().nospace() << query.value(0).toString() << "\n"
|
|
<< query.value(1).toString();
|
|
}
|
|
}
|
|
|
|
{
|
|
auto [ok, query] = select("select @@session.character_set_client, @@session.character_set_connection, "
|
|
"@@session.character_set_results, @@session.collation_connection");
|
|
while(query.next()) {
|
|
qDebug().nospace() << query.value(0).toString() << "\n"
|
|
<< query.value(1).toString() << "\n"
|
|
<< query.value(2).toString() << "\n"
|
|
<< query.value(3).toString();
|
|
}
|
|
}
|
|
|
|
{
|
|
auto [ok, query] = select("select @@global.character_set_client, @@global.character_set_connection, "
|
|
"@@global.character_set_results, @@global.collation_connection");
|
|
while(query.next()) {
|
|
qDebug().nospace() << query.value(0).toString() << "\n"
|
|
<< query.value(1).toString() << "\n"
|
|
<< query.value(2).toString() << "\n"
|
|
<< query.value(3).toString();
|
|
}
|
|
}
|
|
|
|
{
|
|
auto [ok, query] = select("select @@global.sql_mode, @@session.sql_mode");
|
|
while(query.next()) {
|
|
qDebug().nospace() << query.value(0).toString() << "\n"
|
|
<< query.value(1).toString();
|
|
}
|
|
}
|
|
|
|
|
|
tmp notes:
|
|
----------
|
|
|
|
message(-------)
|
|
message(XXX config.pri)
|
|
message(PWD: $$PWD)
|
|
message(OUT_PWD: $$OUT_PWD)
|
|
message(_PRO_FILE_PWD_: $$_PRO_FILE_PWD_)
|
|
message(INCLUDEPATH: $$INCLUDEPATH)
|
|
message(-------)
|
|
|
|
|
|
DatabaseConnection:
|
|
---
|
|
|
|
DatabaseConnection(const QString &database = "", const QString tablePrefix = "",
|
|
const QVariantHash &config = {});
|
|
|
|
DatabaseConnection::DatabaseConnection(const QString &database, const QString tablePrefix,
|
|
const QVariantHash &config)
|
|
/* First we will setup the default properties. We keep track of the DB
|
|
name we are connected to since it is needed when some reflective
|
|
type commands are run such as checking whether a table exists. */
|
|
: m_database(database)
|
|
, m_tablePrefix(tablePrefix)
|
|
, m_config(config)
|
|
{
|
|
// auto db = QSqlDatabase::addDatabase("QMYSQL", CONNECTION_NAME);
|
|
// db.setHostName(m_config.find("host").value().toString());
|
|
// db.setDatabaseName(m_config.find("database").value().toString());
|
|
// db.setUserName(m_config.find("username").value().toString());
|
|
// db.setPassword(m_config.find("password").value().toString());
|
|
|
|
// if (m_config.contains("options"))
|
|
// db.setConnectOptions(m_config.find("options").value().toString());
|
|
|
|
// if (!db.open()) {
|
|
// // TODO next solve how to solve this situation, how to inform end user, exception vs error code, ... silverqx
|
|
// qDebug() << "Connect to DB failed :"
|
|
// << db.lastError().text();
|
|
// return;
|
|
// }
|
|
|
|
// Set the connection character set and collation
|
|
// if (config.contains("charset")) {
|
|
// const auto collation = config.value("collation").toString();
|
|
// const auto &collate = QStringLiteral(" collate '%1'").arg(collation);
|
|
// select(QStringLiteral("set names '%1'%2")
|
|
// .arg(config.find("charset").value().toString(),
|
|
// collate.isEmpty() ? "" : collate));
|
|
// }
|
|
|
|
/* Next, we will check to see if a timezone has been specified in this config
|
|
and if it has we will issue a statement to modify the timezone with the
|
|
database. Setting this DB timezone is an optional configuration item. */
|
|
// if (config.contains("timezone"))
|
|
// select(QStringLiteral("set time_zone='%1'")
|
|
// .arg(config.find("timezone").value().toString()));
|
|
|
|
// Set the modes for the connection.
|
|
// if (config.contains("strict")
|
|
// && config.find("strict").value().toBool()
|
|
// ) {
|
|
// // Obtain MySQL version
|
|
// auto [ok, query] = selectOne("select version()");
|
|
|
|
// if (ok) {
|
|
// QString strictMode;
|
|
|
|
// /* NO_AUTO_CREATE_USER was removed in 8.0.11 */
|
|
// if (QVersionNumber::fromString(query.value(0).toString())
|
|
// >= QVersionNumber(8, 0, 11)
|
|
// )
|
|
// strictMode =
|
|
// "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,"
|
|
// "NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,"
|
|
// "NO_ENGINE_SUBSTITUTION'";
|
|
// else
|
|
// strictMode =
|
|
// "set session sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,"
|
|
// "NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,"
|
|
// "NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
|
|
|
|
// select(strictMode);
|
|
// }
|
|
// }
|
|
// else
|
|
// select("set session sql_mode='NO_ENGINE_SUBSTITUTION'");
|
|
}
|
|
|
|
|
|
EntityManager.hpp:
|
|
------------------
|
|
|
|
#ifndef ENTITYMANAGER_H
|
|
#define ENTITYMANAGER_H
|
|
|
|
#include "orm/databaseconnection.hpp"
|
|
#include "orm/repositoryfactory.hpp"
|
|
|
|
#ifdef TINYORM_COMMON_NAMESPACE
|
|
namespace TINYORM_COMMON_NAMESPACE
|
|
{
|
|
#endif
|
|
namespace Orm
|
|
{
|
|
|
|
/*! The EntityManager is the central access point to ORM functionality. */
|
|
class SHAREDLIB_EXPORT EntityManager final
|
|
{
|
|
Q_DISABLE_COPY(EntityManager)
|
|
|
|
public:
|
|
EntityManager(const QVariantHash &config);
|
|
EntityManager(DatabaseConnection &connection);
|
|
~EntityManager();
|
|
|
|
/*! Factory method to create EntityManager instances. */
|
|
static EntityManager create(const QVariantHash &config);
|
|
|
|
/*! Gets the repository for an entity class. */
|
|
template<typename Repository>
|
|
QSharedPointer<Repository> getRepository() const;
|
|
|
|
/*! Create a new QSqlQuery. */
|
|
QSqlQuery query() const;
|
|
/*! Get a new query builder instance. */
|
|
QSharedPointer<QueryBuilder> queryBuilder() const;
|
|
/*! Check database connection and show warnings when the state changed. */
|
|
bool pingDatabase();
|
|
/*! Start a new database transaction. */
|
|
bool transaction();
|
|
/*! Commit the active database transaction. */
|
|
bool commit();
|
|
/*! Rollback the active database transaction. */
|
|
bool rollback();
|
|
/*! Start a new named transaction savepoint. */
|
|
bool savepoint(const QString &id);
|
|
/*! Rollback to a named transaction savepoint. */
|
|
bool rollbackToSavepoint(const QString &id);
|
|
|
|
/*! Get underlying database connection. */
|
|
inline DatabaseConnection &connection() const
|
|
{ return m_db; }
|
|
|
|
protected:
|
|
/*! Factory method to create DatabaseConnection instances. */
|
|
static DatabaseConnection &
|
|
createConnection(const QVariantHash &config);
|
|
|
|
private:
|
|
/*! The database connection used by the EntityManager. */
|
|
DatabaseConnection &m_db;
|
|
/*! The repository factory used to create dynamic repositories. */
|
|
RepositoryFactory m_repositoryFactory;
|
|
};
|
|
|
|
template<typename Repository>
|
|
QSharedPointer<Repository> EntityManager::getRepository() const
|
|
{
|
|
return m_repositoryFactory.getRepository<Repository>();
|
|
}
|
|
|
|
} // namespace Orm
|
|
#ifdef TINYORM_COMMON_NAMESPACE
|
|
} // namespace TINYORM_COMMON_NAMESPACE
|
|
#endif
|
|
|
|
#endif // ENTITYMANAGER_H
|
|
|
|
|
|
EntityManager.cpp:
|
|
------------------
|
|
|
|
#include "orm/entitymanager.hpp"
|
|
|
|
#include <QtSql/QSqlQuery>
|
|
|
|
#ifdef TINYORM_COMMON_NAMESPACE
|
|
namespace TINYORM_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.hpp
|
|
in the baserepository.hpp file.
|
|
*/
|
|
|
|
EntityManager::EntityManager(const QVariantHash &config)
|
|
: m_db(createConnection(config))
|
|
, m_repositoryFactory(*this)
|
|
{}
|
|
|
|
EntityManager::EntityManager(DatabaseConnection &connection)
|
|
: m_db(connection)
|
|
, m_repositoryFactory(*this)
|
|
{}
|
|
|
|
EntityManager::~EntityManager()
|
|
{
|
|
DatabaseConnection::freeInstance();
|
|
}
|
|
|
|
EntityManager EntityManager::create(const QVariantHash &config)
|
|
{
|
|
return EntityManager(createConnection(config));
|
|
}
|
|
|
|
QSqlQuery EntityManager::query() const
|
|
{
|
|
return m_db.query();
|
|
}
|
|
|
|
QSharedPointer<QueryBuilder> EntityManager::queryBuilder() const
|
|
{
|
|
return m_db.query();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
DatabaseConnection &
|
|
EntityManager::createConnection(const QVariantHash &config)
|
|
{
|
|
return DatabaseConnection::create(config.find("database").value().toString(),
|
|
config.find("prefix").value().toString(),
|
|
config);
|
|
}
|
|
|
|
} // namespace Orm
|
|
#ifdef TINYORM_COMMON_NAMESPACE
|
|
} // namespace TINYORM_COMMON_NAMESPACE
|
|
#endif
|