mirror of
https://github.com/silverqx/TinyORM.git
synced 2026-02-12 05:19:15 -06:00
added SQLite database support 🔥🚀✨
- all tests are ran against SQLite test database too 🎉🔥 - removed default models connection for tests, because I'm testing models for all database connections and the connection is controlled by global test data, so every test is ran against all the supported databases - added test code controlled by TINYORM_TESTS_CODE preprocessor macro, which enables connection override in the BaseModel - build tests only when the "CONFIG += build_tests" was added to the qmake command added php script to create databases - script creates MySQL and SQLite databases - script populate this databases with test data - connection credentials are provided through the DB_ env. variables other changes - added api to enable statement counters for the given connections - added BaseModel::truncate test
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -74,3 +74,6 @@ Thumbs.db
|
||||
*.dll
|
||||
*.exe
|
||||
|
||||
# Test's Database
|
||||
**/tests/**/testdata/vendor/
|
||||
**/tests/**/testdata/dotenv.ps1
|
||||
|
||||
41
NOTES.txt
41
NOTES.txt
@@ -40,13 +40,14 @@ Common:
|
||||
- desirable : feature which is extremely wanted
|
||||
- dilemma : some sort of a fuckup
|
||||
- duplicate : duplicate code
|
||||
- feature : some feature to implement, perpend before feature described below
|
||||
- future : task which has lower priority, because still much to do
|
||||
- mistake : bad decision during prototyping 😭
|
||||
- move : c++ move semantics
|
||||
- mystery : don't know why that stuff is happening, find out what's up
|
||||
- now : do it before commit
|
||||
- next : next thing in the row to do after commit
|
||||
- overflow : add check code, eg when size_t to int coversion
|
||||
- overflow : add check code, eg when size_t to int conversion
|
||||
- perf : performance
|
||||
- production : check before deploy to production
|
||||
- reliability : make things more robust and reliable
|
||||
@@ -67,6 +68,7 @@ Features related/to implement:
|
||||
- primarykey dilema : different types for primary keys
|
||||
- relations : relations related 🤓
|
||||
- scopes : query scopes
|
||||
- table prefix : table prefix in the query grammar
|
||||
|
||||
|
||||
Versions info:
|
||||
@@ -75,6 +77,14 @@ Versions info:
|
||||
- based on Laravel v8.26.1
|
||||
|
||||
|
||||
Maintenance:
|
||||
------------
|
||||
|
||||
- from time to time try:
|
||||
- compile without PCH
|
||||
- compile with Qt6, I have still problem with clazy
|
||||
|
||||
|
||||
constructor copy/move snippet:
|
||||
------------------------------
|
||||
|
||||
@@ -143,14 +153,17 @@ 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")},
|
||||
// {"url", qEnvironmentVariable("MYSQL_DATABASE_URL")},
|
||||
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
|
||||
{"port", qEnvironmentVariable("DB_MYSQL_PORT", "3306")},
|
||||
{"database", qEnvironmentVariable("DB_MYSQL_DATABASE", "")},
|
||||
{"username", qEnvironmentVariable("DB_MYSQL_USERNAME", "root")},
|
||||
{"password", qEnvironmentVariable("DB_MYSQL_PASSWORD", "")},
|
||||
// {"unix_socket", qEnvironmentVariable("DB_MYSQL_SOCKET", "")},
|
||||
{"charset", qEnvironmentVariable("DB_MYSQL_CHARSET", "utf8mb4")},
|
||||
{"collation", qEnvironmentVariable("DB_MYSQL_COLLATION", "utf8mb4_unicode_ci")},
|
||||
// {"collation", qEnvironmentVariable("DB_MYSQL_COLLATION", "utf8mb4_0900_ai_ci")},
|
||||
// {"timezone", "+00:00"},
|
||||
// {"prefix", ""},
|
||||
// {"prefix_indexes", true},
|
||||
{"strict", true},
|
||||
@@ -158,6 +171,16 @@ QHash<QString, QVariant> config {
|
||||
{"options", ""},
|
||||
};
|
||||
|
||||
QHash<QString, QVariant> config {
|
||||
{"driver", "QSQLITE"},
|
||||
{"database", qEnvironmentVariable("DB_SQLITE_DATABASE", "")},
|
||||
{"prefix", ""},
|
||||
{"options", QVariantHash()},
|
||||
{"foreign_key_constraints", qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS",
|
||||
"true")},
|
||||
{"check_database_exists", true},
|
||||
};
|
||||
|
||||
|
||||
DatabaseConnection debug code:
|
||||
------------------------------
|
||||
|
||||
10
TinyOrm.pro
10
TinyOrm.pro
@@ -1,7 +1,9 @@
|
||||
TEMPLATE = subdirs
|
||||
|
||||
SUBDIRS += \
|
||||
src \
|
||||
tests \
|
||||
SUBDIRS += src
|
||||
|
||||
tests.depends = src
|
||||
# Can be enabled by CONFIG += build_tests when the qmake.exe for the project is called
|
||||
build_tests {
|
||||
SUBDIRS += tests
|
||||
tests.depends = src
|
||||
}
|
||||
|
||||
@@ -165,6 +165,10 @@ By default, all TinyOrm models will use the default database connection that is
|
||||
QString u_connection {"sqlite"};
|
||||
};
|
||||
|
||||
In special cases, when you want to query the database through a different connection, you can use `BaseModel::on` method, which takes the connection name as the first argument:
|
||||
|
||||
auto user = User::on("sqlite")->find(1);
|
||||
|
||||
<a name="retrieving-models"></a>
|
||||
## Retrieving Models
|
||||
|
||||
|
||||
@@ -11,22 +11,27 @@ HEADERS += \
|
||||
$$PWD/orm/connectors/connector.hpp \
|
||||
$$PWD/orm/connectors/connectorinterface.hpp \
|
||||
$$PWD/orm/connectors/mysqlconnector.hpp \
|
||||
$$PWD/orm/connectors/sqliteconnector.hpp \
|
||||
$$PWD/orm/databaseconnection.hpp \
|
||||
$$PWD/orm/databasemanager.hpp \
|
||||
$$PWD/orm/db.hpp \
|
||||
$$PWD/orm/grammar.hpp \
|
||||
$$PWD/orm/invalidformaterror.hpp \
|
||||
$$PWD/orm/logquery.hpp \
|
||||
$$PWD/orm/mysqlconnection.hpp \
|
||||
$$PWD/orm/ormdomainerror.hpp \
|
||||
$$PWD/orm/orminvalidargumenterror.hpp \
|
||||
$$PWD/orm/ormlogicerror.hpp \
|
||||
$$PWD/orm/ormruntimeerror.hpp \
|
||||
$$PWD/orm/ormtypes.hpp \
|
||||
$$PWD/orm/query/expression.hpp \
|
||||
$$PWD/orm/query/grammars/grammar.hpp \
|
||||
$$PWD/orm/query/grammars/mysqlgrammar.hpp \
|
||||
$$PWD/orm/query/grammars/sqlitegrammar.hpp \
|
||||
$$PWD/orm/query/joinclause.hpp \
|
||||
$$PWD/orm/query/querybuilder.hpp \
|
||||
$$PWD/orm/queryerror.hpp \
|
||||
$$PWD/orm/sqlerror.hpp \
|
||||
$$PWD/orm/sqliteconnection.hpp \
|
||||
$$PWD/orm/sqltransactionerror.hpp \
|
||||
$$PWD/orm/support/configurationoptionsparser.hpp \
|
||||
$$PWD/orm/tiny/basemodel.hpp \
|
||||
|
||||
@@ -13,7 +13,18 @@ namespace Orm
|
||||
{
|
||||
|
||||
class DatabaseConnection;
|
||||
namespace Query
|
||||
{
|
||||
class Builder;
|
||||
|
||||
namespace Grammars
|
||||
{
|
||||
class Grammar;
|
||||
}
|
||||
}
|
||||
using QueryBuilder = Query::Builder;
|
||||
using QueryGrammar = Query::Grammars::Grammar;
|
||||
|
||||
/*! Counts executed statements in a current connection. */
|
||||
struct StatementsCounter
|
||||
{
|
||||
@@ -25,12 +36,6 @@ namespace Orm
|
||||
int transactional = -1;
|
||||
};
|
||||
|
||||
namespace Query
|
||||
{
|
||||
class Builder;
|
||||
}
|
||||
using QueryBuilder = Query::Builder;
|
||||
|
||||
class ConnectionInterface
|
||||
{
|
||||
public:
|
||||
@@ -116,7 +121,7 @@ namespace Query
|
||||
|
||||
/*! Prepare the query bindings for execution. */
|
||||
virtual QVector<QVariant>
|
||||
prepareBindings(const QVector<QVariant> &bindings) const = 0;
|
||||
prepareBindings(QVector<QVariant> bindings) const = 0;
|
||||
|
||||
/*! Check database connection and show warnings when the state changed. */
|
||||
virtual bool pingDatabase() = 0;
|
||||
@@ -130,8 +135,11 @@ namespace Query
|
||||
/*! Get the name of the connected database. */
|
||||
virtual const QString &getDatabaseName() const = 0;
|
||||
|
||||
/*! Set the query grammar to the default implementation. */
|
||||
virtual void useDefaultQueryGrammar() = 0;
|
||||
|
||||
/*! Get the query grammar used by the connection. */
|
||||
virtual const Grammar &getQueryGrammar() const = 0;
|
||||
virtual const QueryGrammar &getQueryGrammar() const = 0;
|
||||
|
||||
/* Queries execution time counter */
|
||||
/*! Determine whether we're counting queries execution time. */
|
||||
@@ -160,6 +168,9 @@ namespace Query
|
||||
virtual StatementsCounter takeStatementsCounter() = 0;
|
||||
/*! Reset the number of executed queries. */
|
||||
virtual DatabaseConnection &resetStatementsCounter() = 0;
|
||||
|
||||
/*! Return the connection's driver name. */
|
||||
virtual QString driverName() = 0;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
|
||||
@@ -31,7 +31,8 @@ namespace Orm::Connectors
|
||||
/*! Get the QSqlDatabase connection options based on the configuration. */
|
||||
QString getOptions(const QVariantHash &config) const;
|
||||
|
||||
/*! Parse connection options. */
|
||||
/*! Parse and validate QSqlDatabase connection options, called from
|
||||
the ConfigurationOptionsParser. */
|
||||
virtual void parseConfigOptions(QVariantHash &options) const = 0;
|
||||
/*! Get the QSqlDatabase connection options for the current connector. */
|
||||
virtual const QVariantHash &getConnectorOptions() const = 0;
|
||||
|
||||
@@ -19,7 +19,8 @@ namespace Orm::Connectors
|
||||
|
||||
/*! Get the QSqlDatabase connection options for the current connector. */
|
||||
const QVariantHash &getConnectorOptions() const override;
|
||||
/*! Parse connection options. */
|
||||
/*! Parse and validate QSqlDatabase connection options, called from
|
||||
the ConfigurationOptionsParser. */
|
||||
void parseConfigOptions(QVariantHash &options) const override;
|
||||
|
||||
protected:
|
||||
|
||||
44
include/orm/connectors/sqliteconnector.hpp
Normal file
44
include/orm/connectors/sqliteconnector.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef SQLITECONNECTOR_HPP
|
||||
#define SQLITECONNECTOR_HPP
|
||||
|
||||
#include "orm/connectors/connector.hpp"
|
||||
#include "orm/connectors/connectorinterface.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Connectors
|
||||
{
|
||||
|
||||
class SQLiteConnector final : public ConnectorInterface, public Connector
|
||||
{
|
||||
public:
|
||||
/*! Establish a database connection. */
|
||||
ConnectionName connect(const QVariantHash &config) const override;
|
||||
|
||||
/*! Get the QSqlDatabase connection options for the current connector. */
|
||||
const QVariantHash &getConnectorOptions() const override;
|
||||
/*! Parse and validate QSqlDatabase connection options, called from
|
||||
the ConfigurationOptionsParser. */
|
||||
void parseConfigOptions(QVariantHash &options) const override;
|
||||
|
||||
protected:
|
||||
/*! Set the connection foreign key constraints. */
|
||||
void configureForeignKeyConstraints(const QSqlDatabase &connection,
|
||||
const QVariantHash &config) const;
|
||||
|
||||
private:
|
||||
/*! Check whether the SQLite database file exists. */
|
||||
void checkDatabaseExists(const QVariantHash &config) const;
|
||||
|
||||
/*! The default QSqlDatabase connection options for the SQLiteConnector. */
|
||||
inline static const QVariantHash m_options {};
|
||||
};
|
||||
|
||||
} // namespace Orm::Connectors
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // SQLITECONNECTOR_HPP
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "orm/concerns/detectslostconnections.hpp"
|
||||
#include "orm/connectioninterface.hpp"
|
||||
#include "orm/connectors/connectorinterface.hpp"
|
||||
#include "orm/grammar.hpp"
|
||||
#include "orm/query/grammars/grammar.hpp"
|
||||
#include "orm/queryerror.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
@@ -112,7 +112,7 @@ namespace Orm
|
||||
|
||||
/*! Prepare the query bindings for execution. */
|
||||
QVector<QVariant>
|
||||
prepareBindings(const QVector<QVariant> &bindings) const override;
|
||||
prepareBindings(QVector<QVariant> bindings) const override;
|
||||
/*! Bind values to their parameters in the given statement. */
|
||||
void bindValues(QSqlQuery &query,
|
||||
const QVector<QVariant> &bindings) const;
|
||||
@@ -128,15 +128,21 @@ namespace Orm
|
||||
/*! Disconnect from the underlying PDO connection. */
|
||||
void disconnect();
|
||||
|
||||
/*! Get the default query grammar instance. */
|
||||
virtual std::unique_ptr<QueryGrammar> getDefaultQueryGrammar() const = 0;
|
||||
|
||||
/*! Get the database connection name. */
|
||||
inline const QString getName() const override
|
||||
{ return getConfig("name").toString(); }
|
||||
/*! Get the name of the connected database. */
|
||||
inline const QString &getDatabaseName() const override
|
||||
{ return m_database; }
|
||||
/*! Set the query grammar to the default implementation. */
|
||||
inline void useDefaultQueryGrammar() override
|
||||
{ m_queryGrammar = getDefaultQueryGrammar(); }
|
||||
/*! Get the query grammar used by the connection. */
|
||||
inline const Grammar &getQueryGrammar() const override
|
||||
{ return m_queryGrammar; }
|
||||
inline const QueryGrammar &getQueryGrammar() const override
|
||||
{ return *m_queryGrammar; }
|
||||
|
||||
// TODO duplicate, extract to some internal types silverqx
|
||||
/*! Reconnector lambda type. */
|
||||
@@ -147,7 +153,7 @@ namespace Orm
|
||||
/*! Get an option from the configuration options. */
|
||||
QVariant getConfig(const QString &option) const;
|
||||
/*! Get the configuration for the current connection. */
|
||||
QVariant getConfig() const;
|
||||
const QVariantHash &getConfig() const;
|
||||
|
||||
/* Queries execution time counter */
|
||||
/*! Determine whether we're counting queries execution time. */
|
||||
@@ -177,6 +183,9 @@ namespace Orm
|
||||
/*! Reset the number of executed queries. */
|
||||
DatabaseConnection &resetStatementsCounter() override;
|
||||
|
||||
/*! Return the connection's driver name. */
|
||||
QString driverName() override;
|
||||
|
||||
protected:
|
||||
/*! Callback type used in the run() methods. */
|
||||
template<typename Result>
|
||||
@@ -201,12 +210,18 @@ namespace Orm
|
||||
/*! Reset in transaction state and savepoints. */
|
||||
DatabaseConnection &resetTransactions();
|
||||
|
||||
/*! Log database disconnected, examined during MySQL ping. */
|
||||
void logDisconnected();
|
||||
/*! Log database connected, examined during MySQL ping. */
|
||||
void logConnected();
|
||||
|
||||
/*! The active QSqlDatabase connection name. */
|
||||
std::optional<Connectors::ConnectionName> m_qtConnection;
|
||||
/*! The QSqlDatabase connection resolver. */
|
||||
std::function<Connectors::ConnectionName()> m_qtConnectionResolver;
|
||||
/*! The name of the connected database. */
|
||||
const QString m_database;
|
||||
// TODO feature, table prefix silverqx
|
||||
/*! The table prefix for the connection. */
|
||||
const QString m_tablePrefix {""};
|
||||
/*! The database connection configuration options. */
|
||||
@@ -247,10 +262,6 @@ namespace Orm
|
||||
const QVector<QVariant> &bindings,
|
||||
const RunCallback<Result> &callback) const;
|
||||
|
||||
/*! Log database disconnected, examined during MySQL ping. */
|
||||
void logDisconnected();
|
||||
/*! Log database connected, examined during MySQL ping. */
|
||||
void logConnected();
|
||||
/*! Log a transaction query into the connection's query log. */
|
||||
void logTransactionQuery(const QString &query,
|
||||
const std::optional<qint64> &elapsed);
|
||||
@@ -267,7 +278,7 @@ namespace Orm
|
||||
/*! Active savepoints counter. */
|
||||
uint m_savepoints = 0;
|
||||
/*! The query grammar implementation. */
|
||||
Grammar m_queryGrammar;
|
||||
std::unique_ptr<QueryGrammar> m_queryGrammar;
|
||||
|
||||
#ifdef TINYORM_DEBUG_SQL
|
||||
/*! Indicates whether logging of sql queries is enabled. */
|
||||
@@ -276,7 +287,6 @@ namespace Orm
|
||||
/*! Indicates whether logging of sql queries is enabled. */
|
||||
const bool m_debugSql = false;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
template<typename Result>
|
||||
|
||||
@@ -111,6 +111,8 @@ namespace Query
|
||||
QStringList supportedDrivers() const;
|
||||
/*! Returns a list containing the names of all connections. */
|
||||
QStringList connectionNames() const;
|
||||
/*! Returns a list containing the names of opened connections. */
|
||||
QStringList openedConnectionNames() const;
|
||||
|
||||
/*! Get the default connection name. */
|
||||
const QString &getDefaultConnection() const override;
|
||||
@@ -150,6 +152,17 @@ namespace Query
|
||||
/*! Reset queries execution time on all active connections. */
|
||||
void resetAllElapsedCounters();
|
||||
|
||||
/*! Enable counting queries execution time on given connections. */
|
||||
inline void enableElapsedCounters(const QStringList &connections);
|
||||
/*! Disable counting queries execution time on given connections. */
|
||||
inline void disableElapsedCounters(const QStringList &connections);
|
||||
/*! Obtain queries execution time from given connections. */
|
||||
inline qint64 getElapsedCounters(const QStringList &connections);
|
||||
/*! Obtain and reset queries execution time on given connections. */
|
||||
inline qint64 takeElapsedCounters(const QStringList &connections);
|
||||
/*! Reset queries execution time on given connections. */
|
||||
inline void resetElapsedCounters(const QStringList &connections);
|
||||
|
||||
/* Queries executed counter */
|
||||
/*! Determine whether we're counting the number of executed queries. */
|
||||
bool countingStatements(const QString &connection = "");
|
||||
@@ -183,6 +196,17 @@ namespace Query
|
||||
/*! Reset the number of executed queries on all active connections. */
|
||||
void resetAllStatementCounters();
|
||||
|
||||
/*! Enable counting the number of executed queries on given connections. */
|
||||
inline void enableStatementCounters(const QStringList &connections);
|
||||
/*! Disable counting the number of executed queries on given connections. */
|
||||
inline void disableStatementCounters(const QStringList &connections);
|
||||
/*! Obtain the number of executed queries on given connections. */
|
||||
inline StatementsCounter getStatementCounters(const QStringList &connections);
|
||||
/*! Obtain and reset the number of executed queries on given connections. */
|
||||
inline StatementsCounter takeStatementCounters(const QStringList &connections);
|
||||
/*! Reset the number of executed queries on given connections. */
|
||||
inline void resetStatementCounters(const QStringList &connections);
|
||||
|
||||
protected:
|
||||
/*! Default connection name. */
|
||||
static const char *defaultConnectionName;
|
||||
|
||||
@@ -58,6 +58,8 @@ namespace Orm
|
||||
static const QStringList supportedDrivers();
|
||||
/*! Returns a list containing the names of all connections. */
|
||||
static QStringList connectionNames();
|
||||
/*! Returns a list containing the names of opened connections. */
|
||||
static QStringList openedConnectionNames();
|
||||
|
||||
/*! Get the default connection name. */
|
||||
static const QString &getDefaultConnection();
|
||||
@@ -155,6 +157,17 @@ namespace Orm
|
||||
/*! Reset queries execution time on all active connections. */
|
||||
static void resetAllElapsedCounters();
|
||||
|
||||
/*! Enable counting queries execution time on given connections. */
|
||||
static void enableElapsedCounters(const QStringList &connections);
|
||||
/*! Disable counting queries execution time on given connections. */
|
||||
static void disableElapsedCounters(const QStringList &connections);
|
||||
/*! Obtain queries execution time from given connections. */
|
||||
static qint64 getElapsedCounters(const QStringList &connections);
|
||||
/*! Obtain and reset queries execution time on given connections. */
|
||||
static qint64 takeElapsedCounters(const QStringList &connections);
|
||||
/*! Reset queries execution time on given connections. */
|
||||
static void resetElapsedCounters(const QStringList &connections);
|
||||
|
||||
/* Queries executed counter */
|
||||
/*! Determine whether we're counting the number of executed queries. */
|
||||
static bool
|
||||
@@ -188,6 +201,17 @@ namespace Orm
|
||||
static StatementsCounter takeAllStatementCounters();
|
||||
/*! Reset the number of executed queries on all active connections. */
|
||||
static void resetAllStatementCounters();
|
||||
|
||||
/*! Enable counting the number of executed queries on given connections. */
|
||||
static void enableStatementCounters(const QStringList &connections);
|
||||
/*! Disable counting the number of executed queries on given connections. */
|
||||
static void disableStatementCounters(const QStringList &connections);
|
||||
/*! Obtain the number of executed queries on given connections. */
|
||||
static StatementsCounter getStatementCounters(const QStringList &connections);
|
||||
/*! Obtain and reset the number of executed queries on given connections. */
|
||||
static StatementsCounter takeStatementCounters(const QStringList &connections);
|
||||
/*! Reset the number of executed queries on given connections. */
|
||||
static void resetStatementCounters(const QStringList &connections);
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
|
||||
@@ -18,6 +18,15 @@ namespace Orm
|
||||
const QString &database = "", const QString tablePrefix = "",
|
||||
const QVariantHash &config = {});
|
||||
inline virtual ~MySqlConnection() = default;
|
||||
|
||||
/*! Get the default query grammar instance. */
|
||||
std::unique_ptr<QueryGrammar> getDefaultQueryGrammar() const override;
|
||||
|
||||
/*! Determine if the connected database is a MariaDB database. */
|
||||
inline bool isMaria();
|
||||
|
||||
/*! Check database connection and show warnings when the state changed. */
|
||||
bool pingDatabase() override;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
|
||||
26
include/orm/orminvalidargumenterror.hpp
Normal file
26
include/orm/orminvalidargumenterror.hpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef ORMINVALIDARGUMENTERROR_H
|
||||
#define ORMINVALIDARGUMENTERROR_H
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "export.hpp"
|
||||
#include "orm/ormlogicerror.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
class SHAREDLIB_EXPORT OrmInvalidArgumentError : public OrmLogicError
|
||||
{
|
||||
using OrmLogicError::OrmLogicError;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // ORMINVALIDARGUMENTERROR_H
|
||||
@@ -8,45 +8,76 @@
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
class SHAREDLIB_EXPORT Grammar
|
||||
{
|
||||
public:
|
||||
virtual ~Grammar() = default;
|
||||
|
||||
/*! Compile a select query into SQL. */
|
||||
QString compileSelect(QueryBuilder &query) const;
|
||||
|
||||
/*! Compile an insert statement into SQL. */
|
||||
QString compileInsert(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const;
|
||||
virtual 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;
|
||||
virtual QString
|
||||
compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const;
|
||||
// TODO postgres, sequence silverqx
|
||||
/*! Compile an insert and get ID statement into SQL. */
|
||||
inline QString
|
||||
compileInsertGetId(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
{ return compileInsert(query, values); }
|
||||
|
||||
/*! Compile an update statement into SQL. */
|
||||
QString compileUpdate(const QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const;
|
||||
virtual QString
|
||||
compileUpdate(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;
|
||||
virtual QString compileDelete(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;
|
||||
|
||||
/*! Compile a truncate table statement into SQL. Returns a map of
|
||||
the query string and bindings. */
|
||||
virtual std::unordered_map<QString, QVector<QVariant>>
|
||||
compileTruncate(const QueryBuilder &query) const;
|
||||
|
||||
/*! Get the format for database stored dates. */
|
||||
const QString &getDateFormat() const;
|
||||
|
||||
/*! Get the grammar specific operators. */
|
||||
virtual const QVector<QString> &getOperators() const;
|
||||
|
||||
protected:
|
||||
/*! Compile the "order by" portions of the query. */
|
||||
QString compileOrders(const QueryBuilder &query) const;
|
||||
/*! Compile the "limit" portions of the query. */
|
||||
QString compileLimit(const QueryBuilder &query) const;
|
||||
|
||||
/*! Compile the columns for an update statement. */
|
||||
virtual QString
|
||||
compileUpdateColumns(const QVector<UpdateItem> &values) const;
|
||||
/*! Compile an update statement without joins into SQL. */
|
||||
virtual QString
|
||||
compileUpdateWithoutJoins(const QueryBuilder &query, const QString &table,
|
||||
const QString &columns, const QString &wheres) const;
|
||||
|
||||
/*! Compile a delete statement without joins into SQL. */
|
||||
virtual QString
|
||||
compileDeleteWithoutJoins(const QueryBuilder &query, const QString &table,
|
||||
const QString &wheres) const;
|
||||
|
||||
// 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;
|
||||
@@ -62,6 +93,11 @@ namespace Orm
|
||||
/*! Remove the leading boolean from a statement. */
|
||||
QString removeLeadingBoolean(QString statement) const;
|
||||
|
||||
/*! Get an alias from the 'from' clause. */
|
||||
QString getAliasFromFrom(const QString &from) const;
|
||||
/*! Get the column name without the table name, a string after last dot. */
|
||||
QString unqualifyColumn(const QString &column) const;
|
||||
|
||||
private:
|
||||
/*! The components necessary for a select clause. */
|
||||
using SelectComponentsVector = QVector<QString>;
|
||||
@@ -101,8 +137,7 @@ namespace Orm
|
||||
/*! Compile the "where" portions of the query. */
|
||||
QString compileWheres(const QueryBuilder &query) const;
|
||||
/*! Get the vector of all the where clauses for the query. */
|
||||
QVector<QString>
|
||||
compileWheresToVector(const QueryBuilder &query) const;
|
||||
QVector<QString> compileWheresToVector(const QueryBuilder &query) const;
|
||||
/*! Format the where clause statements into one string. */
|
||||
QString concatenateWhereClauses(const QueryBuilder &query,
|
||||
const QVector<QString> &sql) const;
|
||||
@@ -135,35 +170,20 @@ namespace Orm
|
||||
/*! Compile a "where not null" clause. */
|
||||
QString whereNotNull(const WhereConditionItem &where) const;
|
||||
|
||||
/*! Compile the "order by" portions of the query. */
|
||||
QString compileOrders(const QueryBuilder &query) const;
|
||||
/*! Compile the query orders to the vector. */
|
||||
QVector<QString> compileOrdersToVector(const QueryBuilder &query) const;
|
||||
/*! Compile the "limit" portions of the query. */
|
||||
QString compileLimit(const QueryBuilder &query) const;
|
||||
/*! Compile the "offset" portions of the query. */
|
||||
QString compileOffset(const QueryBuilder &query) const;
|
||||
|
||||
/*! Compile a insert values lists. */
|
||||
QVector<QString> compileInsertToVector(const QVector<QVariantMap> &values) const;
|
||||
/*! Compile an insert statement into SQL. */
|
||||
QString
|
||||
compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values,
|
||||
bool ignore) const;
|
||||
QVector<QString>
|
||||
compileInsertToVector(const QVector<QVariantMap> &values) 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;
|
||||
44
include/orm/query/grammars/mysqlgrammar.hpp
Normal file
44
include/orm/query/grammars/mysqlgrammar.hpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#ifndef MYSQLGRAMMAR_H
|
||||
#define MYSQLGRAMMAR_H
|
||||
|
||||
#include "orm/query/grammars/grammar.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
class SHAREDLIB_EXPORT MySqlGrammar : public Grammar
|
||||
{
|
||||
public:
|
||||
/*! Compile an insert statement into SQL. */
|
||||
QString compileInsert(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const override;
|
||||
/*! Compile an insert ignore statement into SQL. */
|
||||
QString compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const override;
|
||||
|
||||
/*! Get the grammar specific operators. */
|
||||
const QVector<QString> &getOperators() const override;
|
||||
|
||||
protected:
|
||||
/*! Compile an update statement without joins into SQL. */
|
||||
QString
|
||||
compileUpdateWithoutJoins(
|
||||
const QueryBuilder &query, const QString &table,
|
||||
const QString &columns, const QString &wheres) const override;
|
||||
|
||||
/*! Compile a delete statement without joins into SQL. */
|
||||
QString
|
||||
compileDeleteWithoutJoins(const QueryBuilder &query, const QString &table,
|
||||
const QString &wheres) const override;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // MYSQLGRAMMAR_H
|
||||
57
include/orm/query/grammars/sqlitegrammar.hpp
Normal file
57
include/orm/query/grammars/sqlitegrammar.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef SQLITEGRAMMAR_H
|
||||
#define SQLITEGRAMMAR_H
|
||||
|
||||
#include "orm/query/grammars/grammar.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
class SHAREDLIB_EXPORT SQLiteGrammar : public Grammar
|
||||
{
|
||||
|
||||
public:
|
||||
/*! Compile an insert ignore statement into SQL. */
|
||||
QString compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const override;
|
||||
|
||||
/*! Compile an update statement into SQL. */
|
||||
QString compileUpdate(QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const override;
|
||||
|
||||
/*! Compile a delete statement into SQL. */
|
||||
QString compileDelete(QueryBuilder &query) const override;
|
||||
|
||||
/*! Compile a truncate table statement into SQL. Returns a map of
|
||||
the query string and bindings. */
|
||||
std::unordered_map<QString, QVector<QVariant>>
|
||||
compileTruncate(const QueryBuilder &query) const override;
|
||||
|
||||
/*! Get the grammar specific operators. */
|
||||
const QVector<QString> &getOperators() const override;
|
||||
|
||||
protected:
|
||||
/*! Compile the columns for an update statement. */
|
||||
QString compileUpdateColumns(const QVector<UpdateItem> &values) const override;
|
||||
|
||||
private:
|
||||
/*! Compile an update statement with joins or limit into SQL. */
|
||||
QString compileUpdateWithJoinsOrLimit(QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const;
|
||||
|
||||
/*! Compile a delete statement with joins or limit into SQL. */
|
||||
QString compileDeleteWithJoinsOrLimit(QueryBuilder &query) const;
|
||||
|
||||
/*! The grammar specific operators. */
|
||||
const QVector<QString> m_operators {"sounds like"};
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // SQLITEGRAMMAR_H
|
||||
@@ -16,20 +16,24 @@ namespace TINYORM_COMMON_NAMESPACE
|
||||
namespace Orm
|
||||
{
|
||||
class ConnectionInterface;
|
||||
class Grammar;
|
||||
}
|
||||
|
||||
namespace Orm::Query
|
||||
{
|
||||
|
||||
class JoinClause;
|
||||
|
||||
namespace Grammars
|
||||
{
|
||||
class Grammar;
|
||||
}
|
||||
using QueryGrammar = Query::Grammars::Grammar;
|
||||
|
||||
// TODO add support for subqueries, first in where() silverqx
|
||||
// TODO add inRandomOrder() silverqx
|
||||
class SHAREDLIB_EXPORT Builder
|
||||
{
|
||||
public:
|
||||
Builder(ConnectionInterface &connection, const Grammar &grammar);
|
||||
Builder(ConnectionInterface &connection, const QueryGrammar &grammar);
|
||||
// WARNING solve pure virtual dtor vs default silverqx
|
||||
/* Need to be the polymorphic type because of dynamic_cast<>
|
||||
in Grammar::concatenateWhereClauses(). */
|
||||
@@ -86,7 +90,7 @@ namespace Orm::Query
|
||||
std::tuple<int, QSqlQuery> remove(const quint64 id);
|
||||
|
||||
/*! Run a truncate statement on the table. */
|
||||
std::tuple<bool, QSqlQuery> truncate();
|
||||
void truncate();
|
||||
|
||||
/* Select */
|
||||
/*! Set the columns to be selected. */
|
||||
@@ -266,7 +270,7 @@ namespace Orm::Query
|
||||
inline ConnectionInterface &getConnection() const
|
||||
{ return m_connection; }
|
||||
/*! Get the query grammar instance. */
|
||||
inline const Grammar &getGrammar() const
|
||||
inline const QueryGrammar &getGrammar() const
|
||||
{ return m_grammar; }
|
||||
|
||||
/*! Get the current query value bindings as flattened QVector. */
|
||||
@@ -375,7 +379,7 @@ namespace Orm::Query
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
/*! The database query grammar instance. */
|
||||
const Grammar &m_grammar;
|
||||
const QueryGrammar &m_grammar;
|
||||
|
||||
/*! The current query value bindings.
|
||||
Order is crucial here because of that QMap with an enum struct is used. */
|
||||
@@ -407,6 +411,7 @@ namespace Orm::Query
|
||||
QVector<HavingConditionItem> m_havings;
|
||||
/*! The orderings for the query. */
|
||||
QVector<OrderByItem> m_orders;
|
||||
// BUG I think that limit can be also negative silverqx
|
||||
/*! The maximum number of records to return. */
|
||||
int m_limit = -1;
|
||||
/*! The number of records to skip. */
|
||||
|
||||
31
include/orm/sqliteconnection.hpp
Normal file
31
include/orm/sqliteconnection.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef SQLITECONNECTION_HPP
|
||||
#define SQLITECONNECTION_HPP
|
||||
|
||||
#include "orm/databaseconnection.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
class SHAREDLIB_EXPORT SQLiteConnection final : public DatabaseConnection
|
||||
{
|
||||
public:
|
||||
SQLiteConnection(
|
||||
const std::function<Connectors::ConnectionName()> &connection,
|
||||
const QString &database = "", const QString tablePrefix = "",
|
||||
const QVariantHash &config = {});
|
||||
inline virtual ~SQLiteConnection() = default;
|
||||
|
||||
/*! Get the default query grammar instance. */
|
||||
std::unique_ptr<QueryGrammar> getDefaultQueryGrammar() const override;
|
||||
};
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
|
||||
#endif // SQLITECONNECTION_HPP
|
||||
@@ -19,19 +19,25 @@ namespace Connectors
|
||||
namespace Support
|
||||
{
|
||||
|
||||
/*! Validate, prepare, and merge QSqlDatabase connection options, these are
|
||||
the settings passed to the QSqlDatabase::setConnectOptions(). */
|
||||
class ConfigurationOptionsParser
|
||||
{
|
||||
public:
|
||||
ConfigurationOptionsParser(const Connectors::Connector &connector);
|
||||
|
||||
/*! Parse the database configuration, validate, prepare, and merge connection options. */
|
||||
/*! Parse the database configuration, validate, prepare, and merge connection
|
||||
options. */
|
||||
QString parseConfiguration(const QVariantHash &config) const;
|
||||
protected:
|
||||
/*! Validate the 'options' configuration type, has to be QString or QVariantHash. */
|
||||
/*! Validate the 'options' configuration type, has to be the QString or
|
||||
QVariantHash. */
|
||||
void validateConfigOptions(const QVariant &options) const;
|
||||
/*! Prepare options for parseConfigOptions() function, convert to the QVariantHash if needed. */
|
||||
/*! Prepare options for parseConfigOptions() function, convert to
|
||||
the QVariantHash if needed. */
|
||||
QVariantHash prepareConfigOptions(const QVariant &options) const;
|
||||
/*! Merge the TinyORM connector options with user's provided connection options defined in the config. */
|
||||
/*! Merge the TinyORM connector options with user's provided connection
|
||||
options defined in the config. */
|
||||
QVariantHash mergeOptions(const QVariantHash &connectortOptions,
|
||||
const QVariantHash &preparedConfigOptions) const;
|
||||
/*! Stringify merged options. */
|
||||
|
||||
@@ -39,6 +39,16 @@ namespace Relations {
|
||||
|
||||
using QueryBuilder = Query::Builder;
|
||||
|
||||
#ifdef TINYORM_TESTS_CODE
|
||||
/*! Used by tests to override connection in the BaseModel. */
|
||||
struct ConnectionOverride
|
||||
{
|
||||
/*! The connection to use in the BaseModel, this data member is picked up
|
||||
in the BaseModel::getConnectionName(). */
|
||||
inline static QString connection = "";
|
||||
};
|
||||
#endif
|
||||
|
||||
// TODO decide/unify when to use class/typename keywords for templates silverqx
|
||||
// TODO concept, AllRelations can not contain type defined in "Model" parameter silverqx
|
||||
// TODO next test no relation behavior silverqx
|
||||
@@ -86,6 +96,8 @@ namespace Relations {
|
||||
/* Static operations on a model instance */
|
||||
/*! Begin querying the model. */
|
||||
static std::unique_ptr<TinyBuilder<Model>> query();
|
||||
/*! Begin querying the model on a given connection. */
|
||||
static std::unique_ptr<TinyBuilder<Model>> on(const QString &connection = "");
|
||||
|
||||
/* TinyBuilder proxy methods */
|
||||
/*! Get all of the models from the database. */
|
||||
@@ -164,7 +176,7 @@ namespace Relations {
|
||||
static std::size_t destroy(QVariant id);
|
||||
|
||||
/*! Run a truncate statement on the table. */
|
||||
static std::tuple<bool, QSqlQuery> truncate();
|
||||
static void truncate();
|
||||
|
||||
/* Select */
|
||||
/*! Set the columns to be selected. */
|
||||
@@ -469,8 +481,7 @@ namespace Relations {
|
||||
|
||||
/* Getters / Setters */
|
||||
/*! Get the current connection name for the model. */
|
||||
inline const QString &getConnectionName() const
|
||||
{ return model().u_connection; }
|
||||
const QString &getConnectionName() const;
|
||||
/*! Get the database connection for the model. */
|
||||
inline ConnectionInterface &getConnection() const
|
||||
{ return m_resolver->connection(getConnectionName()); }
|
||||
@@ -856,7 +867,9 @@ namespace Relations {
|
||||
// TODO future Default Attribute Values, can not be u_attributes because of CRTP, because BaseModel is initialized first and u_attributes are uninitialized, the best I've come up with was BaseModel.init() and init default attrs. from there silverqx
|
||||
/*! The model's attributes. */
|
||||
QVector<AttributeItem> m_attributes;
|
||||
/*! The model attribute's original state. */
|
||||
/*! The model attribute's original state.
|
||||
On the model from many-to-many relation also contains all pivot values,
|
||||
that is normal. */
|
||||
QVector<AttributeItem> m_original;
|
||||
/*! The changed model attributes. */
|
||||
QVector<AttributeItem> m_changes;
|
||||
@@ -1020,6 +1033,21 @@ namespace Relations {
|
||||
return Model().newQuery();
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
std::unique_ptr<TinyBuilder<Model>>
|
||||
BaseModel<Model, AllRelations...>::on(const QString &connection)
|
||||
{
|
||||
/* First we will just create a fresh instance of this model, and then we can
|
||||
set the connection on the model so that it is used for the queries we
|
||||
execute, as well as being set on every relation we retrieve without
|
||||
a custom connection name. */
|
||||
Model instance;
|
||||
|
||||
instance.setConnection(connection);
|
||||
|
||||
return instance.newQuery();
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
QVector<Model>
|
||||
BaseModel<Model, AllRelations...>::all(const QStringList &columns)
|
||||
@@ -1208,10 +1236,9 @@ namespace Relations {
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
std::tuple<bool, QSqlQuery>
|
||||
BaseModel<Model, AllRelations...>::truncate()
|
||||
void BaseModel<Model, AllRelations...>::truncate()
|
||||
{
|
||||
return query()->truncate();
|
||||
query()->truncate();
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
@@ -2489,6 +2516,21 @@ namespace Relations {
|
||||
parent, attributes, table, exists);
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
const QString &
|
||||
BaseModel<Model, AllRelations...>::getConnectionName() const
|
||||
{
|
||||
#ifdef TINYORM_TESTS_CODE
|
||||
// Used from tests to override connection
|
||||
if (const auto &connection = ConnectionOverride::connection;
|
||||
!connection.isEmpty()
|
||||
)
|
||||
return connection;
|
||||
#endif
|
||||
|
||||
return model().u_connection;
|
||||
}
|
||||
|
||||
template<typename Model, typename ...AllRelations>
|
||||
QString BaseModel<Model, AllRelations...>::getTable() const
|
||||
{
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace Relations
|
||||
std::tuple<int, QSqlQuery> deleteModels() const;
|
||||
|
||||
/*! Run a truncate statement on the table. */
|
||||
std::tuple<bool, QSqlQuery> truncate() const;
|
||||
void truncate() const;
|
||||
|
||||
/* Select */
|
||||
/*! Set the columns to be selected. */
|
||||
@@ -709,10 +709,9 @@ namespace Relations
|
||||
}
|
||||
|
||||
template<class Model, class Related>
|
||||
std::tuple<bool, QSqlQuery>
|
||||
Relation<Model, Related>::truncate() const
|
||||
void Relation<Model, Related>::truncate() const
|
||||
{
|
||||
return m_query->truncate();
|
||||
m_query->truncate();
|
||||
}
|
||||
|
||||
template<class Model, class Related>
|
||||
|
||||
@@ -120,7 +120,7 @@ namespace Relations
|
||||
std::tuple<int, QSqlQuery> deleteModels();
|
||||
|
||||
/*! Run a truncate statement on the table. */
|
||||
std::tuple<bool, QSqlQuery> truncate();
|
||||
void truncate();
|
||||
|
||||
/* Select */
|
||||
/*! Set the columns to be selected. */
|
||||
@@ -627,9 +627,9 @@ namespace Relations
|
||||
}
|
||||
|
||||
template<typename Model>
|
||||
std::tuple<bool, QSqlQuery> Builder<Model>::truncate()
|
||||
void Builder<Model>::truncate()
|
||||
{
|
||||
return toBase().truncate();
|
||||
toBase().truncate();
|
||||
}
|
||||
|
||||
template<typename Model>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
#include "orm/connectors/connectionfactory.hpp"
|
||||
|
||||
#include "orm/connectors/mysqlconnector.hpp"
|
||||
#include "orm/connectors/sqliteconnector.hpp"
|
||||
#include "orm/mysqlconnection.hpp"
|
||||
#include "orm/sqliteconnection.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
@@ -34,8 +36,8 @@ ConnectionFactory::createConnector(const QVariantHash &config) const
|
||||
return std::make_unique<MySqlConnector>();
|
||||
// else if (driver == "QPSQL")
|
||||
// return std::make_unique<PostgresConnector>();
|
||||
// else if (driver == "QSQLITE")
|
||||
// return std::make_unique<SQLiteConnector>();
|
||||
else if (driver == "QSQLITE")
|
||||
return std::make_unique<SQLiteConnector>();
|
||||
// else if (driver == "SQLSRV")
|
||||
// return std::make_unique<SqlServerConnector>();
|
||||
else
|
||||
@@ -124,14 +126,13 @@ ConnectionFactory::createQSqlDatabaseResolverWithHosts(const QVariantHash &confi
|
||||
}
|
||||
|
||||
std::function<ConnectionName()>
|
||||
ConnectionFactory::createQSqlDatabaseResolverWithoutHosts(const QVariantHash &) const
|
||||
ConnectionFactory::createQSqlDatabaseResolverWithoutHosts(
|
||||
const QVariantHash &config) const
|
||||
{
|
||||
throw std::domain_error(
|
||||
"createQSqlDatabaseResolverWithoutHosts() not yet implemented.");
|
||||
|
||||
// return function () use ($config) {
|
||||
// return $this->createConnector($config)->connect($config);
|
||||
// };
|
||||
return [this, &config]() -> ConnectionName
|
||||
{
|
||||
return createConnector(config)->connect(config);
|
||||
};
|
||||
}
|
||||
|
||||
std::unique_ptr<DatabaseConnection>
|
||||
@@ -145,8 +146,8 @@ ConnectionFactory::createConnection(
|
||||
return std::make_unique<MySqlConnection>(connection, database, prefix, config);
|
||||
// else if (driver == "QPSQL")
|
||||
// return std::make_unique<PostgresConnection>(connection, database, prefix, config);
|
||||
// else if (driver == "QSQLITE")
|
||||
// return std::make_unique<SQLiteConnection>(connection, database, prefix, config);
|
||||
else if (driver == "QSQLITE")
|
||||
return std::make_unique<SQLiteConnection>(connection, database, prefix, config);
|
||||
// else if (driver == "SQLSRV")
|
||||
// return std::make_unique<SqlServerConnection>(connection, database, prefix, config);
|
||||
else
|
||||
@@ -157,6 +158,7 @@ ConnectionFactory::createConnection(
|
||||
QStringList ConnectionFactory::parseHosts(const QVariantHash &config) const
|
||||
{
|
||||
if (!config.contains("host"))
|
||||
// TODO now unify exception, std::domain_error is for user code, create own exceptions and use InvalidArgumentError, or runtime/logic error silverqx
|
||||
throw std::domain_error("Database 'host' configuration parameter is required.");
|
||||
|
||||
const auto hosts = config["host"].value<QStringList>();
|
||||
|
||||
@@ -48,7 +48,10 @@ QString Connector::getOptions(const QVariantHash &config) const
|
||||
any default connection options which are common for all drivers, instead
|
||||
every driver has it's own connection options.
|
||||
So I have divided it into two options, one are config options which are
|
||||
defined by the user and others are connector options. */
|
||||
defined by the user and others are connector options.
|
||||
Options defined by a user are in the config's 'options' parameter and
|
||||
connector options are defined in the connector itself as 'm_options'
|
||||
data member. */
|
||||
// Validate, prepare, and merge connection options
|
||||
return Support::ConfigurationOptionsParser(*this)
|
||||
.parseConfiguration(config);
|
||||
@@ -60,18 +63,19 @@ Connector::addQSqlDatabaseConnection(const QString &name, const QVariantHash &co
|
||||
{
|
||||
QSqlDatabase db;
|
||||
|
||||
db = QSqlDatabase::addDatabase(config["driver"].toString(), name);
|
||||
// TODO now change all QVariant conversions to value<>() silverqx
|
||||
db = QSqlDatabase::addDatabase(config["driver"].value<QString>(), name);
|
||||
|
||||
db.setHostName(config["host"].toString());
|
||||
db.setHostName(config["host"].value<QString>());
|
||||
|
||||
if (config.contains("database"))
|
||||
db.setDatabaseName(config["database"].toString());
|
||||
db.setDatabaseName(config["database"].value<QString>());
|
||||
if (config.contains("username"))
|
||||
db.setUserName(config["username"].toString());
|
||||
db.setUserName(config["username"].value<QString>());
|
||||
if (config.contains("password"))
|
||||
db.setPassword(config["password"].toString());
|
||||
db.setPassword(config["password"].value<QString>());
|
||||
if (config.contains("port"))
|
||||
db.setPort(config["port"].toUInt());
|
||||
db.setPort(config["port"].value<uint>());
|
||||
|
||||
db.setConnectOptions(options);
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <QtSql/QSqlQuery>
|
||||
#include <QVersionNumber>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
@@ -19,7 +17,7 @@ MySqlConnector::connect(const QVariantHash &config) const
|
||||
|
||||
/* We need to grab the QSqlDatabse options that should be used while making
|
||||
the brand new connection instance. The QSqlDatabase options control various
|
||||
aspects of the connection's behavior, and can be override by the developers. */
|
||||
aspects of the connection's behavior, and can be overridden by the developers. */
|
||||
const auto options = getOptions(config);
|
||||
|
||||
// Create and open new database connection
|
||||
@@ -142,8 +140,9 @@ void MySqlConnector::setModes(const QSqlDatabase &connection,
|
||||
}
|
||||
}
|
||||
else {
|
||||
QSqlQuery query(QStringLiteral("set session sql_mode='NO_ENGINE_SUBSTITUTION'"),
|
||||
connection);
|
||||
QSqlQuery query(
|
||||
QStringLiteral("set session sql_mode='NO_ENGINE_SUBSTITUTION'"),
|
||||
connection);
|
||||
query.exec();
|
||||
}
|
||||
}
|
||||
|
||||
94
src/orm/connectors/sqliteconnector.cpp
Normal file
94
src/orm/connectors/sqliteconnector.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
#include "orm/connectors/sqliteconnector.hpp"
|
||||
|
||||
#include <QtSql/QSqlQuery>
|
||||
|
||||
#include "orm/orminvalidargumenterror.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Connectors
|
||||
{
|
||||
|
||||
ConnectionName
|
||||
SQLiteConnector::connect(const QVariantHash &config) const
|
||||
{
|
||||
const auto name = config["name"].toString();
|
||||
|
||||
const auto options = getOptions(config);
|
||||
|
||||
/* SQLite supports "in-memory" databases that only last as long as the owning
|
||||
connection does. These are useful for tests or for short lifetime store
|
||||
querying. In-memory databases may only have a single open connection. */
|
||||
if (config["database"] == ":memory:") {
|
||||
// sqlite :memory: driver
|
||||
createConnection(name, config, options);
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/* Here we'll verify that the SQLite database exists before going any further
|
||||
as the developer probably wants to know if the database exists and this
|
||||
SQLite driver will not throw any exception if it does not by default. */
|
||||
checkDatabaseExists(config);
|
||||
|
||||
// Create and open new database connection
|
||||
const auto connection = createConnection(name, config, options);
|
||||
|
||||
// Foreign key constraints
|
||||
configureForeignKeyConstraints(connection, config);
|
||||
|
||||
/* Return only connection name, because QSqlDatabase documentation doesn't
|
||||
recommend to store QSqlDatabase instance as a class data member, we can
|
||||
simply obtain the connection by QSqlDatabase::connection() when needed. */
|
||||
return name;
|
||||
}
|
||||
|
||||
const QVariantHash &
|
||||
SQLiteConnector::getConnectorOptions() const
|
||||
{
|
||||
return m_options;
|
||||
}
|
||||
|
||||
void SQLiteConnector::parseConfigOptions(QVariantHash &) const
|
||||
{}
|
||||
|
||||
void SQLiteConnector::configureForeignKeyConstraints(
|
||||
const QSqlDatabase &connection, const QVariantHash &config) const
|
||||
{
|
||||
// This ensures default SQLite behavior
|
||||
if (!config.contains("foreign_key_constraints"))
|
||||
return;
|
||||
|
||||
const auto foreignKeyConstraints =
|
||||
config["foreign_key_constraints"].value<bool>() ? "ON" : "OFF";
|
||||
|
||||
QSqlQuery query(connection);
|
||||
// TODO feature, schema builder, foreign key constraints silverqx
|
||||
query.prepare(QStringLiteral("PRAGMA foreign_keys = ?;"));
|
||||
query.addBindValue(foreignKeyConstraints);
|
||||
|
||||
query.exec();
|
||||
}
|
||||
|
||||
void SQLiteConnector::checkDatabaseExists(const QVariantHash &config) const
|
||||
{
|
||||
const auto path = config["database"].value<QString>();
|
||||
|
||||
// Default behavior is to check database existence
|
||||
bool checkDatabaseExists = true;
|
||||
if (const auto &configCheckDatabase = config["check_database_exists"];
|
||||
configCheckDatabase.isValid() && !configCheckDatabase.isNull()
|
||||
)
|
||||
checkDatabaseExists = config["check_database_exists"].value<bool>();
|
||||
|
||||
if (checkDatabaseExists && !QFile::exists(path))
|
||||
throw OrmInvalidArgumentError(
|
||||
QStringLiteral("SQLite Database file '%1' does not exist.").arg(path));
|
||||
}
|
||||
|
||||
} // namespace Orm::Connectors
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
@@ -1,11 +1,7 @@
|
||||
#include "orm/databaseconnection.hpp"
|
||||
|
||||
#include <QtSql/QSqlDriver>
|
||||
#include <QtSql/QSqlError>
|
||||
|
||||
#include "mysql.h"
|
||||
|
||||
#include "orm/logquery.hpp"
|
||||
#include "orm/query/grammars/grammar.hpp"
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
#include "orm/sqltransactionerror.hpp"
|
||||
|
||||
@@ -16,8 +12,10 @@ namespace TINYORM_COMMON_NAMESPACE
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
const char *DatabaseConnection::defaultConnectionName = const_cast<char *>("tinyorm_default");
|
||||
const char *DatabaseConnection::SAVEPOINT_NAMESPACE = const_cast<char *>("tinyorm_savepoint");
|
||||
const char *
|
||||
DatabaseConnection::defaultConnectionName = const_cast<char *>("tinyorm_default");
|
||||
const char *
|
||||
DatabaseConnection::SAVEPOINT_NAMESPACE = const_cast<char *>("tinyorm_savepoint");
|
||||
|
||||
/*!
|
||||
\class DatabaseConnection
|
||||
@@ -52,7 +50,7 @@ DatabaseConnection::DatabaseConnection(
|
||||
QSharedPointer<QueryBuilder>
|
||||
DatabaseConnection::table(const QString &table, const QString &as)
|
||||
{
|
||||
auto builder = QSharedPointer<QueryBuilder>::create(*this, m_queryGrammar);
|
||||
auto builder = QSharedPointer<QueryBuilder>::create(*this, *m_queryGrammar);
|
||||
|
||||
builder->from(table, as);
|
||||
|
||||
@@ -61,7 +59,7 @@ DatabaseConnection::table(const QString &table, const QString &as)
|
||||
|
||||
QSharedPointer<QueryBuilder> DatabaseConnection::query()
|
||||
{
|
||||
return QSharedPointer<QueryBuilder>::create(*this, m_queryGrammar);
|
||||
return QSharedPointer<QueryBuilder>::create(*this, *m_queryGrammar);
|
||||
}
|
||||
|
||||
bool DatabaseConnection::beginTransaction()
|
||||
@@ -404,11 +402,42 @@ QSqlQuery DatabaseConnection::getQtQuery()
|
||||
|
||||
// TODO perf, modify bindings directly and return reference, debug impact silverqx
|
||||
QVector<QVariant>
|
||||
DatabaseConnection::prepareBindings(const QVector<QVariant> &bindings) const
|
||||
DatabaseConnection::prepareBindings(QVector<QVariant> bindings) const
|
||||
{
|
||||
// const auto &grammar = getQueryGrammar();
|
||||
|
||||
// TODO future, here will be implemented datetime values silverqx
|
||||
for (auto &binding : bindings) {
|
||||
// Nothing to convert
|
||||
if (!binding.isValid() || binding.isNull())
|
||||
continue;
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
switch (binding.typeId()) {
|
||||
#else
|
||||
switch (binding.userType()) {
|
||||
#endif
|
||||
/* We need to transform all instances of DateTimeInterface into the actual
|
||||
date string. Each query grammar maintains its own date string format
|
||||
so we'll just ask the grammar for the format to get from the date. */
|
||||
// CUR solve this datetime problem silverqx
|
||||
// case QMetaType::QTime:
|
||||
// case QMetaType::QDate:
|
||||
// case QMetaType::QDateTime:
|
||||
//// if ($value instanceof DateTimeInterface) {
|
||||
//// $bindings[$key] = $value->format($grammar->getDateFormat());
|
||||
// break;
|
||||
|
||||
/* Even if eg. the MySQL driver handles this internally, I will do it this way
|
||||
to be consistent across all supported databases. */
|
||||
case QMetaType::Bool:
|
||||
binding.convert(QMetaType::Int);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bindings;
|
||||
}
|
||||
|
||||
@@ -463,60 +492,9 @@ DatabaseConnection::hitTransactionalCounters(const QElapsedTimer &timer)
|
||||
|
||||
bool DatabaseConnection::pingDatabase()
|
||||
{
|
||||
auto qtConnection = getQtConnection();
|
||||
|
||||
const auto getMysqlHandle = [&qtConnection]() -> MYSQL *
|
||||
{
|
||||
if (auto driverHandle = qtConnection.driver()->handle();
|
||||
qstrcmp(driverHandle.typeName(), "MYSQL*") == 0
|
||||
)
|
||||
return *static_cast<MYSQL **>(driverHandle.data());
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const auto mysqlPing = [getMysqlHandle]() -> bool
|
||||
{
|
||||
auto mysqlHandle = getMysqlHandle();
|
||||
if (mysqlHandle == nullptr)
|
||||
return false;
|
||||
|
||||
const auto ping = mysql_ping(mysqlHandle);
|
||||
const 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 (qtConnection.isOpen() && mysqlPing()) {
|
||||
logConnected();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The database connection was lost
|
||||
logDisconnected();
|
||||
|
||||
// Database connection have to be closed manually
|
||||
// isOpen() check is called in MySQL driver
|
||||
qtConnection.close();
|
||||
|
||||
// Reset in transaction state and the savepoints counter
|
||||
resetTransactions();
|
||||
|
||||
return false;
|
||||
throw OrmRuntimeError(
|
||||
QStringLiteral("The '%1' database driver doesn't support ping command.")
|
||||
.arg(driverName()));
|
||||
}
|
||||
|
||||
void DatabaseConnection::reconnect() const
|
||||
@@ -554,7 +532,8 @@ QVariant DatabaseConnection::getConfig(const QString &option) const
|
||||
return m_config.value(option);
|
||||
}
|
||||
|
||||
QVariant DatabaseConnection::getConfig() const
|
||||
const QVariantHash &
|
||||
DatabaseConnection::getConfig() const
|
||||
{
|
||||
return m_config;
|
||||
}
|
||||
@@ -659,6 +638,11 @@ DatabaseConnection &DatabaseConnection::resetStatementsCounter()
|
||||
return *this;
|
||||
}
|
||||
|
||||
QString DatabaseConnection::driverName()
|
||||
{
|
||||
return getQtConnection().driverName();
|
||||
}
|
||||
|
||||
void DatabaseConnection::reconnectIfMissingConnection() const
|
||||
{
|
||||
if (!m_qtConnectionResolver) {
|
||||
@@ -677,19 +661,6 @@ DatabaseConnection &DatabaseConnection::resetTransactions()
|
||||
return *this;
|
||||
}
|
||||
|
||||
QSqlQuery DatabaseConnection::prepareQuery(const QString &queryString)
|
||||
{
|
||||
// Prepare query string
|
||||
auto query = getQtQuery();
|
||||
|
||||
// TODO solve setForwardOnly() in DatabaseConnection class, again this problem 🤔 silverqx
|
||||
// query.setForwardOnly(m_forwardOnly);
|
||||
|
||||
query.prepare(queryString);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
void DatabaseConnection::logDisconnected()
|
||||
{
|
||||
if (m_disconnectedLogged)
|
||||
@@ -715,6 +686,19 @@ void DatabaseConnection::logConnected()
|
||||
qInfo() << "Database connected";
|
||||
}
|
||||
|
||||
QSqlQuery DatabaseConnection::prepareQuery(const QString &queryString)
|
||||
{
|
||||
// Prepare query string
|
||||
auto query = getQtQuery();
|
||||
|
||||
// TODO solve setForwardOnly() in DatabaseConnection class, again this problem 🤔 silverqx
|
||||
// query.setForwardOnly(m_forwardOnly);
|
||||
|
||||
query.prepare(queryString);
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
|
||||
@@ -223,11 +223,16 @@ QStringList DatabaseManager::supportedDrivers() const
|
||||
{
|
||||
// TODO future add method to not only supported drivers, but also check if driver is available/loadable by qsqldatabase silverqx
|
||||
// aaaaaaaaaaaaaachjo 🤔😁
|
||||
return {"mysql"};
|
||||
return {"mysql", "sqlite"};
|
||||
// return {"mysql", "pgsql", "sqlite", "sqlsrv"};
|
||||
}
|
||||
|
||||
QStringList DatabaseManager::connectionNames() const
|
||||
{
|
||||
return m_config.connections.keys();
|
||||
}
|
||||
|
||||
QStringList DatabaseManager::openedConnectionNames() const
|
||||
{
|
||||
QStringList names;
|
||||
// TODO overflow, add check code https://stackoverflow.com/questions/22184403/how-to-cast-the-size-t-to-double-or-int-c/22184657#22184657 silverqx
|
||||
@@ -290,7 +295,7 @@ DatabaseConnection &DatabaseManager::resetElapsedCounter(const QString &connecti
|
||||
|
||||
bool DatabaseManager::anyCountingElapsed()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
const auto connections = openedConnectionNames();
|
||||
|
||||
for (const auto &connectionName : connections)
|
||||
if (connection(connectionName).countingElapsed())
|
||||
@@ -301,29 +306,48 @@ bool DatabaseManager::anyCountingElapsed()
|
||||
|
||||
void DatabaseManager::enableAllElapsedCounters()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).enableElapsedCounter();
|
||||
enableElapsedCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::disableAllElapsedCounters()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
disableElapsedCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
qint64 DatabaseManager::getAllElapsedCounters()
|
||||
{
|
||||
return getElapsedCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
qint64 DatabaseManager::takeAllElapsedCounters()
|
||||
{
|
||||
return takeElapsedCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::resetAllElapsedCounters()
|
||||
{
|
||||
resetElapsedCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::enableElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).enableElapsedCounter();
|
||||
}
|
||||
|
||||
void DatabaseManager::disableElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).disableElapsedCounter();
|
||||
}
|
||||
|
||||
qint64 DatabaseManager::getAllElapsedCounters()
|
||||
qint64 DatabaseManager::getElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
if (!anyCountingElapsed())
|
||||
return -1;
|
||||
|
||||
qint64 elapsed = 0;
|
||||
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
const auto &connection = this->connection(connectionName);
|
||||
|
||||
@@ -334,15 +358,13 @@ qint64 DatabaseManager::getAllElapsedCounters()
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
qint64 DatabaseManager::takeAllElapsedCounters()
|
||||
qint64 DatabaseManager::takeElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
if (!anyCountingElapsed())
|
||||
return -1;
|
||||
|
||||
qint64 elapsed = 0;
|
||||
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
auto &connection = this->connection(connectionName);
|
||||
|
||||
@@ -353,10 +375,8 @@ qint64 DatabaseManager::takeAllElapsedCounters()
|
||||
return elapsed;
|
||||
}
|
||||
|
||||
void DatabaseManager::resetAllElapsedCounters()
|
||||
void DatabaseManager::resetElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
auto &connection = this->connection(connectionName);
|
||||
|
||||
@@ -397,7 +417,7 @@ DatabaseConnection &DatabaseManager::resetStatementsCounter(const QString &conne
|
||||
|
||||
bool DatabaseManager::anyCountingStatements()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
const auto connections = openedConnectionNames();
|
||||
|
||||
for (const auto &connectionName : connections)
|
||||
if (connection(connectionName).countingStatements())
|
||||
@@ -408,29 +428,48 @@ bool DatabaseManager::anyCountingStatements()
|
||||
|
||||
void DatabaseManager::enableAllStatementCounters()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).enableStatementsCounter();
|
||||
enableStatementCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::disableAllStatementCounters()
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
disableStatementCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
StatementsCounter DatabaseManager::getAllStatementCounters()
|
||||
{
|
||||
return getStatementCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
StatementsCounter DatabaseManager::takeAllStatementCounters()
|
||||
{
|
||||
return takeStatementCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::resetAllStatementCounters()
|
||||
{
|
||||
resetStatementCounters(openedConnectionNames());
|
||||
}
|
||||
|
||||
void DatabaseManager::enableStatementCounters(const QStringList &connections)
|
||||
{
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).enableStatementsCounter();
|
||||
}
|
||||
|
||||
void DatabaseManager::disableStatementCounters(const QStringList &connections)
|
||||
{
|
||||
for (const auto &connectionName : connections)
|
||||
connection(connectionName).disableStatementsCounter();
|
||||
}
|
||||
|
||||
StatementsCounter DatabaseManager::getAllStatementCounters()
|
||||
StatementsCounter DatabaseManager::getStatementCounters(const QStringList &connections)
|
||||
{
|
||||
StatementsCounter counter;
|
||||
|
||||
if (!anyCountingStatements())
|
||||
return counter;
|
||||
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
const auto &connection = this->connection(connectionName);
|
||||
|
||||
@@ -446,15 +485,13 @@ StatementsCounter DatabaseManager::getAllStatementCounters()
|
||||
return counter;
|
||||
}
|
||||
|
||||
StatementsCounter DatabaseManager::takeAllStatementCounters()
|
||||
StatementsCounter DatabaseManager::takeStatementCounters(const QStringList &connections)
|
||||
{
|
||||
StatementsCounter counter;
|
||||
|
||||
if (!anyCountingStatements())
|
||||
return counter;
|
||||
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
auto &connection = this->connection(connectionName);
|
||||
|
||||
@@ -470,10 +507,8 @@ StatementsCounter DatabaseManager::takeAllStatementCounters()
|
||||
return counter;
|
||||
}
|
||||
|
||||
void DatabaseManager::resetAllStatementCounters()
|
||||
void DatabaseManager::resetStatementCounters(const QStringList &connections)
|
||||
{
|
||||
const auto connections = connectionNames();
|
||||
|
||||
for (const auto &connectionName : connections) {
|
||||
auto &connection = this->connection(connectionName);
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@ QStringList DB::connectionNames()
|
||||
return manager().connectionNames();
|
||||
}
|
||||
|
||||
QStringList DB::openedConnectionNames()
|
||||
{
|
||||
return manager().openedConnectionNames();
|
||||
}
|
||||
|
||||
const QString &DB::getDefaultConnection()
|
||||
{
|
||||
return manager().getDefaultConnection();
|
||||
@@ -232,6 +237,31 @@ void DB::resetAllElapsedCounters()
|
||||
return manager().resetAllElapsedCounters();
|
||||
}
|
||||
|
||||
void DB::enableElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
manager().enableElapsedCounters(connections);
|
||||
}
|
||||
|
||||
void DB::disableElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
manager().disableElapsedCounters(connections);
|
||||
}
|
||||
|
||||
qint64 DB::getElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
return manager().getElapsedCounters(connections);
|
||||
}
|
||||
|
||||
qint64 DB::takeElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
return manager().takeElapsedCounters(connections);
|
||||
}
|
||||
|
||||
void DB::resetElapsedCounters(const QStringList &connections)
|
||||
{
|
||||
manager().resetElapsedCounters(connections);
|
||||
}
|
||||
|
||||
bool DB::countingStatements(const QString &connection)
|
||||
{
|
||||
return manager().connection(connection).countingStatements();
|
||||
@@ -292,6 +322,31 @@ void DB::resetAllStatementCounters()
|
||||
return manager().resetAllStatementCounters();
|
||||
}
|
||||
|
||||
void DB::enableStatementCounters(const QStringList &connections)
|
||||
{
|
||||
manager().enableStatementCounters(connections);
|
||||
}
|
||||
|
||||
void DB::disableStatementCounters(const QStringList &connections)
|
||||
{
|
||||
manager().disableStatementCounters(connections);
|
||||
}
|
||||
|
||||
StatementsCounter DB::getStatementCounters(const QStringList &connections)
|
||||
{
|
||||
return manager().getStatementCounters(connections);
|
||||
}
|
||||
|
||||
StatementsCounter DB::takeStatementCounters(const QStringList &connections)
|
||||
{
|
||||
return manager().takeStatementCounters(connections);
|
||||
}
|
||||
|
||||
void DB::resetStatementCounters(const QStringList &connections)
|
||||
{
|
||||
manager().resetStatementCounters(connections);
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#include "orm/mysqlconnection.hpp"
|
||||
|
||||
#include <QtSql/QSqlDriver>
|
||||
|
||||
#include "mysql.h"
|
||||
|
||||
#include "orm/query/grammars/mysqlgrammar.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
@@ -13,7 +19,85 @@ MySqlConnection::MySqlConnection(
|
||||
const QVariantHash &config
|
||||
)
|
||||
: DatabaseConnection(connection, database, tablePrefix, config)
|
||||
{}
|
||||
{
|
||||
/* We need to initialize a query grammar that is a very important part
|
||||
of the database abstraction, so we initialize it to the default value
|
||||
while starting. */
|
||||
useDefaultQueryGrammar();
|
||||
}
|
||||
|
||||
std::unique_ptr<QueryGrammar> MySqlConnection::getDefaultQueryGrammar() const
|
||||
{
|
||||
return std::make_unique<Query::Grammars::MySqlGrammar>();
|
||||
}
|
||||
|
||||
bool MySqlConnection::isMaria()
|
||||
{
|
||||
// TODO tests, now add MariaDB tests, install mariadb add connection and run all the tests against mariadb too silverqx
|
||||
static const auto isMaria = std::get<1>(selectOne("select version()")).value(0)
|
||||
.value<QString>().contains("MariaDB");
|
||||
|
||||
return isMaria;
|
||||
}
|
||||
|
||||
bool MySqlConnection::pingDatabase()
|
||||
{
|
||||
auto qtConnection = getQtConnection();
|
||||
|
||||
const auto getMysqlHandle = [&qtConnection]() -> MYSQL *
|
||||
{
|
||||
if (auto driverHandle = qtConnection.driver()->handle();
|
||||
qstrcmp(driverHandle.typeName(), "MYSQL*") == 0
|
||||
)
|
||||
return *static_cast<MYSQL **>(driverHandle.data());
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
const auto mysqlPing = [getMysqlHandle]() -> bool
|
||||
{
|
||||
auto mysqlHandle = getMysqlHandle();
|
||||
if (mysqlHandle == nullptr)
|
||||
return false;
|
||||
|
||||
const auto ping = mysql_ping(mysqlHandle);
|
||||
const 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 (qtConnection.isOpen() && mysqlPing()) {
|
||||
logConnected();
|
||||
return true;
|
||||
}
|
||||
|
||||
// The database connection was lost
|
||||
logDisconnected();
|
||||
|
||||
// Database connection have to be closed manually
|
||||
// isOpen() check is called in MySQL driver
|
||||
qtConnection.close();
|
||||
|
||||
// Reset in transaction state and the savepoints counter
|
||||
resetTransactions();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "orm/grammar.hpp"
|
||||
#include "orm/query/grammars/grammar.hpp"
|
||||
|
||||
#include <QRegularExpression>
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
using JoinClause = Query::JoinClause;
|
||||
|
||||
namespace
|
||||
{
|
||||
// TODO duplicate in moviedetailservice.cpp silverqx
|
||||
@@ -61,16 +59,26 @@ QString Grammar::compileSelect(QueryBuilder &query) const
|
||||
QString Grammar::compileInsert(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
{
|
||||
return compileInsert(query, values, false);
|
||||
// TODO feature, insert with empty values, this code will never be triggered, because check in the QueryBuilder::insert, even all other code works correctly and support empty values silverqx
|
||||
if (values.isEmpty())
|
||||
return QStringLiteral("insert into %1 default values").arg(query.getFrom());
|
||||
|
||||
return QStringLiteral("insert into %1 (%2) values %3").arg(
|
||||
query.getFrom(),
|
||||
// Columns are obtained only from a first QMap
|
||||
columnize(values.at(0).keys()),
|
||||
joinContainer(compileInsertToVector(values),
|
||||
QStringLiteral(", ")));
|
||||
}
|
||||
|
||||
QString Grammar::compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
QString Grammar::compileInsertOrIgnore(const QueryBuilder &,
|
||||
const QVector<QVariantMap> &) const
|
||||
{
|
||||
return compileInsert(query, values, true);
|
||||
throw OrmRuntimeError("This database engine does not support inserting while "
|
||||
"ignoring errors.");
|
||||
}
|
||||
|
||||
QString Grammar::compileUpdate(const QueryBuilder &query,
|
||||
QString Grammar::compileUpdate(QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const
|
||||
{
|
||||
const auto table = query.getFrom();
|
||||
@@ -78,7 +86,7 @@ QString Grammar::compileUpdate(const QueryBuilder &query,
|
||||
const auto wheres = compileWheres(query);
|
||||
|
||||
return query.getJoins().isEmpty()
|
||||
? compileUpdateWithoutJoins(table, columns, wheres)
|
||||
? compileUpdateWithoutJoins(query, table, columns, wheres)
|
||||
: compileUpdateWithJoins(query, table, columns, wheres);
|
||||
}
|
||||
|
||||
@@ -139,12 +147,12 @@ Grammar::prepareBindingsForUpdate(const BindingsMap &bindings,
|
||||
return preparedBindings;
|
||||
}
|
||||
|
||||
QString Grammar::compileDelete(const QueryBuilder &query) const
|
||||
QString Grammar::compileDelete(QueryBuilder &query) const
|
||||
{
|
||||
const auto table = query.getFrom();
|
||||
const auto wheres = compileWheres(query);
|
||||
|
||||
return query.getJoins().isEmpty() ? compileDeleteWithoutJoins(table, wheres)
|
||||
return query.getJoins().isEmpty() ? compileDeleteWithoutJoins(query, table, wheres)
|
||||
: compileDeleteWithJoins(query, table, wheres);
|
||||
}
|
||||
|
||||
@@ -182,9 +190,10 @@ QVector<QVariant> Grammar::prepareBindingsForDelete(const BindingsMap &bindings)
|
||||
return preparedBindings;
|
||||
}
|
||||
|
||||
QString Grammar::compileTruncate(const QueryBuilder &query) const
|
||||
std::unordered_map<QString, QVector<QVariant>>
|
||||
Grammar::compileTruncate(const QueryBuilder &query) const
|
||||
{
|
||||
return QStringLiteral("truncate table %1").arg(query.getFrom());
|
||||
return {{QStringLiteral("truncate table %1").arg(query.getFrom()), {}}};
|
||||
}
|
||||
|
||||
const QString &Grammar::getDateFormat() const
|
||||
@@ -194,6 +203,52 @@ const QString &Grammar::getDateFormat() const
|
||||
return cachedFormat;
|
||||
}
|
||||
|
||||
const QVector<QString> &Grammar::getOperators() const
|
||||
{
|
||||
/* I make it this way, I don't declare it as pure virtual intentionally, this give
|
||||
me oportunity to instantiate the Grammar class eg. in tests. */
|
||||
static const QVector<QString> cachedOperators;
|
||||
|
||||
return cachedOperators;
|
||||
}
|
||||
|
||||
QString Grammar::compileOrders(const QueryBuilder &query) const
|
||||
{
|
||||
return QStringLiteral("order by %1").arg(
|
||||
joinContainer(compileOrdersToVector(query), QStringLiteral(", ")));
|
||||
}
|
||||
|
||||
QString Grammar::compileLimit(const QueryBuilder &query) const
|
||||
{
|
||||
return QStringLiteral("limit %1").arg(query.getLimit());
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
// CUR change to QStringList::join() silverqx
|
||||
return joinContainer(compiledAssignments, QStringLiteral(", "));
|
||||
}
|
||||
|
||||
QString
|
||||
Grammar::compileUpdateWithoutJoins(const QueryBuilder &, const QString &table,
|
||||
const QString &columns, const QString &wheres) const
|
||||
{
|
||||
return QStringLiteral("update %1 set %2 %3").arg(table, columns, wheres);
|
||||
}
|
||||
|
||||
QString
|
||||
Grammar::compileDeleteWithoutJoins(const QueryBuilder &, const QString &table,
|
||||
const QString &wheres) const
|
||||
{
|
||||
return QStringLiteral("delete from %1 %2").arg(table, wheres);
|
||||
}
|
||||
|
||||
Grammar::SelectComponentsVector
|
||||
Grammar::compileComponents(const QueryBuilder &query) const
|
||||
{
|
||||
@@ -359,6 +414,7 @@ Grammar::getWhereMethod(const WhereType whereType) const
|
||||
getBind(&Grammar::whereNull),
|
||||
getBind(&Grammar::whereNotNull),
|
||||
};
|
||||
|
||||
static const auto size = cached.size();
|
||||
|
||||
// Check if whereType is in the range, just for sure 😏
|
||||
@@ -422,24 +478,15 @@ 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());
|
||||
return compiledOrders;
|
||||
}
|
||||
|
||||
QString Grammar::compileOffset(const QueryBuilder &query) const
|
||||
@@ -454,12 +501,33 @@ Grammar::compileInsertToVector(const QVector<QVariantMap> &values) const
|
||||
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::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::compileDeleteWithJoins(const QueryBuilder &query, const QString &table,
|
||||
const QString &wheres) const
|
||||
{
|
||||
const auto alias = getAliasFromFrom(table);
|
||||
|
||||
const auto joins = compileJoins(query);
|
||||
|
||||
/* Alias has to be after the delete keyword and aliased table definition after the
|
||||
from keyword. */
|
||||
return QStringLiteral("delete %1 from %2 %3 %4").arg(alias, table, joins, wheres);
|
||||
}
|
||||
|
||||
QString Grammar::columnize(const QStringList &columns) const
|
||||
{
|
||||
@@ -479,9 +547,7 @@ QString Grammar::columnize(const QStringList &columns, const bool isTorrentsTabl
|
||||
// "total_leechers, remaining, added_on, hash, status, "
|
||||
// "movie_detail_index, savepath"
|
||||
// };
|
||||
static const QString cached {
|
||||
"id, name, size, progress, added_on, hash"
|
||||
};
|
||||
static const QString cached {"id, name, size, progress, added_on, hash"};
|
||||
return cached;
|
||||
}
|
||||
|
||||
@@ -492,6 +558,7 @@ template<typename Container>
|
||||
QString Grammar::parametrize(const Container &values) const
|
||||
{
|
||||
QVector<QString> compiledParameters;
|
||||
|
||||
for (const auto &value : values)
|
||||
compiledParameters << parameter(value);
|
||||
|
||||
@@ -515,63 +582,14 @@ QString Grammar::removeLeadingBoolean(QString statement) const
|
||||
"");
|
||||
}
|
||||
|
||||
QString
|
||||
Grammar::compileInsert(const QueryBuilder &query, const QVector<QVariantMap> &values,
|
||||
const bool ignore) const
|
||||
QString Grammar::getAliasFromFrom(const QString &from) const
|
||||
{
|
||||
Q_ASSERT(values.size() > 0);
|
||||
|
||||
return QStringLiteral("insert%1 into %2 (%3) values %4").arg(
|
||||
ignore ? QStringLiteral(" ignore") : "",
|
||||
query.getFrom(),
|
||||
// Columns are obtained only from a first QMap
|
||||
columnize(values.at(0).keys()),
|
||||
joinContainer(compileInsertToVector(values),
|
||||
QStringLiteral(", ")));
|
||||
return from.split(QStringLiteral(" as ")).last().trimmed();
|
||||
}
|
||||
|
||||
QString Grammar::compileUpdateColumns(const QVector<UpdateItem> &values) const
|
||||
QString Grammar::unqualifyColumn(const QString &column) 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
|
||||
{
|
||||
const auto alias = table.split(QStringLiteral(" as ")).last().trimmed();
|
||||
|
||||
const auto joins = compileJoins(query);
|
||||
|
||||
/* Alias has to be after the delete keyword and aliased table definition after the
|
||||
from keyword. */
|
||||
return QStringLiteral("delete %1 from %2 %3 %4").arg(alias, table, joins, wheres);
|
||||
return column.split(QChar('.')).last().trimmed();
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
74
src/orm/query/grammars/mysqlgrammar.cpp
Normal file
74
src/orm/query/grammars/mysqlgrammar.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#include "orm/query/grammars/mysqlgrammar.hpp"
|
||||
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
QString MySqlGrammar::compileInsert(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
{
|
||||
// MySQL doesn't support 'default values' statement
|
||||
if (values.isEmpty())
|
||||
return Grammar::compileInsert(query, {{}});
|
||||
|
||||
return Grammar::compileInsert(query, values);
|
||||
}
|
||||
|
||||
QString MySqlGrammar::compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
{
|
||||
return compileInsert(query, values).replace(0, 6, QStringLiteral("insert ignore"));
|
||||
}
|
||||
|
||||
const QVector<QString> &MySqlGrammar::getOperators() const
|
||||
{
|
||||
static const QVector<QString> cachedOperators {"sounds like"};
|
||||
|
||||
return cachedOperators;
|
||||
}
|
||||
|
||||
QString
|
||||
MySqlGrammar::compileUpdateWithoutJoins(
|
||||
const QueryBuilder &query, const QString &table,
|
||||
const QString &columns, const QString &wheres) const
|
||||
{
|
||||
auto sql = Grammar::compileUpdateWithoutJoins(query, table, columns, wheres);
|
||||
|
||||
/* When using MySQL, udpate statements may contain order by statements and limits
|
||||
so we will compile both of those here. */
|
||||
if (!query.getOrders().isEmpty())
|
||||
sql += QChar(' ') + compileOrders(query);
|
||||
|
||||
if (query.getLimit() > -1)
|
||||
sql += QChar(' ') + compileLimit(query);
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
QString
|
||||
MySqlGrammar::compileDeleteWithoutJoins(const QueryBuilder &query, const QString &table,
|
||||
const QString &wheres) const
|
||||
{
|
||||
auto sql = Grammar::compileDeleteWithoutJoins(query, table, wheres);
|
||||
|
||||
/* When using MySQL, delete statements may contain order by statements and limits
|
||||
so we will compile both of those here. Once we have finished compiling this
|
||||
we will return the completed SQL statement so it will be executed for us. */
|
||||
if (!query.getOrders().isEmpty())
|
||||
sql += QChar(' ') + compileOrders(query);
|
||||
|
||||
if (query.getLimit() > -1)
|
||||
sql += QChar(' ') + compileLimit(query);
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
101
src/orm/query/grammars/sqlitegrammar.cpp
Normal file
101
src/orm/query/grammars/sqlitegrammar.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "orm/query/grammars/sqlitegrammar.hpp"
|
||||
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm::Query::Grammars
|
||||
{
|
||||
|
||||
QString SQLiteGrammar::compileInsertOrIgnore(const QueryBuilder &query,
|
||||
const QVector<QVariantMap> &values) const
|
||||
{
|
||||
return compileInsert(query, values)
|
||||
.replace(0, 6, QStringLiteral("insert or ignore"));
|
||||
}
|
||||
|
||||
QString SQLiteGrammar::compileUpdate(QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const
|
||||
{
|
||||
if (!query.getJoins().isEmpty() || query.getLimit() > -1)
|
||||
return compileUpdateWithJoinsOrLimit(query, values);
|
||||
|
||||
return Grammar::compileUpdate(query, values);
|
||||
}
|
||||
|
||||
QString SQLiteGrammar::compileDelete(QueryBuilder &query) const
|
||||
{
|
||||
if (!query.getJoins().isEmpty() || query.getLimit() > -1)
|
||||
return compileDeleteWithJoinsOrLimit(query);
|
||||
|
||||
return Grammar::compileDelete(query);
|
||||
}
|
||||
|
||||
std::unordered_map<QString, QVector<QVariant>>
|
||||
SQLiteGrammar::compileTruncate(const QueryBuilder &query) const
|
||||
{
|
||||
const auto &table = query.getFrom();
|
||||
|
||||
return {
|
||||
{"delete from sqlite_sequence where name = ?", {table}},
|
||||
{QStringLiteral("delete from %1").arg(table), {}},
|
||||
};
|
||||
}
|
||||
|
||||
const QVector<QString> &SQLiteGrammar::getOperators() const
|
||||
{
|
||||
static const QVector<QString> cachedOperators {
|
||||
"=", "<", ">", "<=", ">=", "<>", "!=",
|
||||
"like", "not like", "ilike",
|
||||
"&", "|", "<<", ">>",
|
||||
};
|
||||
|
||||
return cachedOperators;
|
||||
}
|
||||
|
||||
QString SQLiteGrammar::compileUpdateColumns(const QVector<UpdateItem> &values) const
|
||||
{
|
||||
QStringList compiledAssignments;
|
||||
|
||||
for (const auto &assignment : values)
|
||||
compiledAssignments << QStringLiteral("%1 = %2").arg(
|
||||
unqualifyColumn(assignment.column),
|
||||
parameter(assignment.value));
|
||||
|
||||
return compiledAssignments.join(", ");
|
||||
}
|
||||
|
||||
QString
|
||||
SQLiteGrammar::compileUpdateWithJoinsOrLimit(QueryBuilder &query,
|
||||
const QVector<UpdateItem> &values) const
|
||||
{
|
||||
const auto &table = query.getFrom();
|
||||
|
||||
const auto columns = compileUpdateColumns(values);
|
||||
|
||||
const auto alias = getAliasFromFrom(table);
|
||||
|
||||
const auto selectSql = compileSelect(query.select(alias + ".rowid"));
|
||||
|
||||
return QStringLiteral("update %1 set %2 where %3 in (%4)")
|
||||
.arg(table, columns, "rowid", selectSql);
|
||||
}
|
||||
|
||||
QString SQLiteGrammar::compileDeleteWithJoinsOrLimit(QueryBuilder &query) const
|
||||
{
|
||||
const auto &table = query.getFrom();
|
||||
|
||||
const auto alias = getAliasFromFrom(table);
|
||||
|
||||
const auto selectSql = compileSelect(query.select(alias + ".rowid"));
|
||||
|
||||
return QStringLiteral("delete from %1 where %2 in (%3)")
|
||||
.arg(table, "rowid", selectSql);
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
@@ -15,7 +15,7 @@ namespace TINYORM_COMMON_NAMESPACE
|
||||
namespace Orm::Query
|
||||
{
|
||||
|
||||
Builder::Builder(ConnectionInterface &connection, const Grammar &grammar)
|
||||
Builder::Builder(ConnectionInterface &connection, const QueryGrammar &grammar)
|
||||
: m_connection(connection)
|
||||
, m_grammar(grammar)
|
||||
{}
|
||||
@@ -165,9 +165,10 @@ std::tuple<int, QSqlQuery> Builder::remove(const quint64 id)
|
||||
return remove();
|
||||
}
|
||||
|
||||
std::tuple<bool, QSqlQuery> Builder::truncate()
|
||||
void Builder::truncate()
|
||||
{
|
||||
return m_connection.statement(m_grammar.compileTruncate(*this));
|
||||
for (const auto &[sql, bindings] : m_grammar.compileTruncate(*this))
|
||||
m_connection.statement(sql, bindings);
|
||||
}
|
||||
|
||||
Builder &Builder::select(const QStringList columns)
|
||||
@@ -222,6 +223,7 @@ Builder &Builder::join(const QString &table, const QString &first,
|
||||
case JoinType::WHERE:
|
||||
join->where(first, comparison, second);
|
||||
break;
|
||||
|
||||
case JoinType::ON:
|
||||
join->on(first, comparison, second);
|
||||
break;
|
||||
@@ -655,11 +657,10 @@ Builder &Builder::addNestedWhereQuery(const QSharedPointer<Builder> &query,
|
||||
|
||||
bool Builder::invalidOperator(const QString &comparison) const
|
||||
{
|
||||
const auto contains = m_operators.contains(comparison);
|
||||
const auto comparison_ = comparison.toLower();
|
||||
|
||||
Q_ASSERT(contains);
|
||||
|
||||
return contains == false;
|
||||
return !m_operators.contains(comparison_) &&
|
||||
!m_grammar.getOperators().contains(comparison_);
|
||||
}
|
||||
|
||||
Builder &Builder::addBinding(const QVariant &binding, const BindingType type)
|
||||
|
||||
33
src/orm/sqliteconnection.cpp
Normal file
33
src/orm/sqliteconnection.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#include "orm/sqliteconnection.hpp"
|
||||
|
||||
#include "orm/query/grammars/sqlitegrammar.hpp"
|
||||
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
namespace TINYORM_COMMON_NAMESPACE
|
||||
{
|
||||
#endif
|
||||
namespace Orm
|
||||
{
|
||||
|
||||
SQLiteConnection::SQLiteConnection(
|
||||
const std::function<Connectors::ConnectionName()> &connection,
|
||||
const QString &database, const QString tablePrefix,
|
||||
const QVariantHash &config
|
||||
)
|
||||
: DatabaseConnection(connection, database, tablePrefix, config)
|
||||
{
|
||||
/* We need to initialize a query grammar that is a very important part
|
||||
of the database abstraction, so we initialize it to the default value
|
||||
while starting. */
|
||||
useDefaultQueryGrammar();
|
||||
}
|
||||
|
||||
std::unique_ptr<QueryGrammar> SQLiteConnection::getDefaultQueryGrammar() const
|
||||
{
|
||||
return std::make_unique<Query::Grammars::SQLiteGrammar>();
|
||||
}
|
||||
|
||||
} // namespace Orm
|
||||
#ifdef TINYORM_COMMON_NAMESPACE
|
||||
} // namespace TINYORM_COMMON_NAMESPACE
|
||||
#endif
|
||||
@@ -24,7 +24,8 @@ ConfigurationOptionsParser::parseConfiguration(const QVariantHash &config) const
|
||||
// Validate options type in the connection configuration
|
||||
validateConfigOptions(configOptions);
|
||||
|
||||
// Prepare options for prepareConfigOptions() function, convert to QVariantHash if needed
|
||||
/* Prepare options for prepareConfigOptions() function, convert to QVariantHash
|
||||
if needed. */
|
||||
QVariantHash preparedConfigOptions = prepareConfigOptions(configOptions);
|
||||
|
||||
// Parse config connection options, driver specific validation/modification
|
||||
|
||||
@@ -6,18 +6,22 @@ SOURCES += \
|
||||
$$PWD/orm/connectors/connectionfactory.cpp \
|
||||
$$PWD/orm/connectors/connector.cpp \
|
||||
$$PWD/orm/connectors/mysqlconnector.cpp \
|
||||
$$PWD/orm/connectors/sqliteconnector.cpp \
|
||||
$$PWD/orm/databaseconnection.cpp \
|
||||
$$PWD/orm/databasemanager.cpp \
|
||||
$$PWD/orm/db.cpp \
|
||||
$$PWD/orm/grammar.cpp \
|
||||
$$PWD/orm/logquery.cpp \
|
||||
$$PWD/orm/mysqlconnection.cpp \
|
||||
$$PWD/orm/ormtypes.cpp \
|
||||
$$PWD/orm/query/expression.cpp \
|
||||
$$PWD/orm/query/grammars/grammar.cpp \
|
||||
$$PWD/orm/query/grammars/mysqlgrammar.cpp \
|
||||
$$PWD/orm/query/grammars/sqlitegrammar.cpp \
|
||||
$$PWD/orm/query/joinclause.cpp \
|
||||
$$PWD/orm/query/querybuilder.cpp \
|
||||
$$PWD/orm/queryerror.cpp \
|
||||
$$PWD/orm/sqlerror.cpp \
|
||||
$$PWD/orm/sqliteconnection.cpp \
|
||||
$$PWD/orm/support/configurationoptionsparser.cpp \
|
||||
$$PWD/orm/tiny/basemodel.cpp \
|
||||
$$PWD/orm/tiny/modelnotfounderror.cpp \
|
||||
|
||||
@@ -37,6 +37,11 @@ DEFINES += TINYORM_DEBUG_SQL
|
||||
# Build as shared library
|
||||
DEFINES += TINYORM_BUILDING_SHARED
|
||||
|
||||
# Enable code needed by tests, eg connection overriding in the BaseModel
|
||||
build_tests {
|
||||
DEFINES += TINYORM_TESTS_CODE
|
||||
}
|
||||
|
||||
# Dependencies include and library paths
|
||||
# ---
|
||||
|
||||
|
||||
@@ -1,53 +1,47 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QtSql/QSqlDriver>
|
||||
#include <QtTest>
|
||||
|
||||
#include "orm/grammar.hpp"
|
||||
#include "orm/db.hpp"
|
||||
#include "orm/query/querybuilder.hpp"
|
||||
|
||||
#include "database.hpp"
|
||||
|
||||
using namespace Orm;
|
||||
using QueryBuilder = Orm::Query::Builder;
|
||||
|
||||
// TODO use QFINDTESTDATA() to load *.sql file? silverqx
|
||||
class tst_QueryBuilder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_QueryBuilder();
|
||||
~tst_QueryBuilder() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void find() const;
|
||||
void limit() const;
|
||||
|
||||
private:
|
||||
/*! Create QueryBuilder instance. */
|
||||
inline QueryBuilder createQuery() const
|
||||
{ return QueryBuilder(m_connection, Grammar()); }
|
||||
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
/*! Create QueryBuilder instance for the given connection. */
|
||||
inline QSharedPointer<QueryBuilder>
|
||||
createQuery(const QString &connection) const
|
||||
{ return DB::connection(connection).query(); }
|
||||
};
|
||||
|
||||
tst_QueryBuilder::tst_QueryBuilder()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
void tst_QueryBuilder::initTestCase_data() const
|
||||
{
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
void tst_QueryBuilder::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_QueryBuilder::cleanupTestCase()
|
||||
{}
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_QueryBuilder::find() const
|
||||
{
|
||||
auto builder = createQuery();
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
auto [ok, query] = builder.from("torrents").find(2);
|
||||
auto builder = createQuery(connection);
|
||||
|
||||
auto [ok, query] = builder->from("torrents").find(2);
|
||||
|
||||
if (!ok)
|
||||
QFAIL("find() query failed.");
|
||||
@@ -58,10 +52,20 @@ void tst_QueryBuilder::find() const
|
||||
|
||||
void tst_QueryBuilder::limit() const
|
||||
{
|
||||
auto builder = createQuery();
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
if (const auto qtConnection = QSqlDatabase::database(connection);
|
||||
!qtConnection.driver()->hasFeature(QSqlDriver::QuerySize)
|
||||
)
|
||||
// ", " to prevent warning about variadic macro
|
||||
QSKIP(QStringLiteral("'%1' driver doesn't support reporting the size "
|
||||
"of a query.")
|
||||
.arg(qtConnection.driverName()).toUtf8().constData(), );
|
||||
|
||||
auto builder = createQuery(connection);
|
||||
|
||||
{
|
||||
auto [ok, query] = builder.from("torrents").limit(1).get({"id"});
|
||||
auto [ok, query] = builder->from("torrents").limit(1).get({"id"});
|
||||
|
||||
if (!ok)
|
||||
QFAIL("limit(1) query failed.");
|
||||
@@ -70,7 +74,7 @@ void tst_QueryBuilder::limit() const
|
||||
}
|
||||
|
||||
{
|
||||
auto [ok, query] = builder.from("torrents").limit(3).get({"id"});
|
||||
auto [ok, query] = builder->from("torrents").limit(3).get({"id"});
|
||||
|
||||
if (!ok)
|
||||
QFAIL("limit(3) query failed.");
|
||||
@@ -79,7 +83,7 @@ void tst_QueryBuilder::limit() const
|
||||
}
|
||||
|
||||
{
|
||||
auto [ok, query] = builder.from("torrents").limit(4).get({"id"});
|
||||
auto [ok, query] = builder->from("torrents").limit(4).get({"id"});
|
||||
|
||||
if (!ok)
|
||||
QFAIL("limit(4) query failed.");
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QtSql/QSqlDriver>
|
||||
#include <QtTest>
|
||||
|
||||
#include "orm/db.hpp"
|
||||
|
||||
#include "models/setting.hpp"
|
||||
#include "models/torrent.hpp"
|
||||
|
||||
#include "database.hpp"
|
||||
|
||||
using namespace Orm;
|
||||
// TODO tests, namespace silverqx
|
||||
using namespace Orm::Tiny;
|
||||
using Orm::QueryError;
|
||||
using Orm::Tiny::ConnectionOverride;
|
||||
using Orm::Tiny::ModelNotFoundError;
|
||||
|
||||
class tst_BaseModel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_BaseModel();
|
||||
~tst_BaseModel() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void save_Insert() const;
|
||||
void save_Insert_WithDefaultValues() const;
|
||||
@@ -77,23 +75,24 @@ private slots:
|
||||
void update_Failed() const;
|
||||
void update_SameValue() const;
|
||||
|
||||
private:
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
void truncate() const;
|
||||
};
|
||||
|
||||
tst_BaseModel::tst_BaseModel()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
void tst_BaseModel::initTestCase_data() const
|
||||
{
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
void tst_BaseModel::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_BaseModel::cleanupTestCase()
|
||||
{}
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_BaseModel::save_Insert() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Torrent torrent;
|
||||
|
||||
auto addedOn = QDateTime::fromString("2020-10-01 20:22:10", Qt::ISODate);
|
||||
@@ -145,6 +144,10 @@ void tst_BaseModel::save_Insert() const
|
||||
|
||||
void tst_BaseModel::save_Insert_WithDefaultValues() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Torrent torrent;
|
||||
|
||||
auto addedOn = QDateTime::fromString("2020-10-01 20:22:10", Qt::ISODate);
|
||||
@@ -193,6 +196,10 @@ void tst_BaseModel::save_Insert_WithDefaultValues() const
|
||||
|
||||
void tst_BaseModel::save_Insert_TableWithoutAutoincrementKey() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Setting setting;
|
||||
|
||||
setting.setAttribute("name", "setting1")
|
||||
@@ -231,6 +238,10 @@ void tst_BaseModel::save_Insert_TableWithoutAutoincrementKey() const
|
||||
|
||||
void tst_BaseModel::save_Update_Success() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentFile = TorrentPreviewableFile::find(4);
|
||||
QVERIFY(torrentFile);
|
||||
QVERIFY(torrentFile->exists);
|
||||
@@ -253,7 +264,7 @@ void tst_BaseModel::save_Update_Success() const
|
||||
QCOMPARE(torrentFileFresh->getAttribute("size"), QVariant(5570));
|
||||
QCOMPARE(torrentFileFresh->getAttribute("progress"), QVariant(860));
|
||||
|
||||
// TODO tests, now remove
|
||||
// Revert
|
||||
torrentFile->setAttribute("filepath", "test3_file1.mkv")
|
||||
.setAttribute("size", 5568)
|
||||
.setAttribute("progress", 870);
|
||||
@@ -262,9 +273,14 @@ void tst_BaseModel::save_Update_Success() const
|
||||
|
||||
void tst_BaseModel::save_Update_WithNullValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto peer = TorrentPeer::find(4);
|
||||
QVERIFY(peer);
|
||||
QVERIFY(peer->exists);
|
||||
QCOMPARE(peer->getAttribute("total_seeds"), QVariant(4));
|
||||
|
||||
peer->setAttribute("total_seeds", QVariant());
|
||||
|
||||
@@ -282,19 +298,28 @@ void tst_BaseModel::save_Update_WithNullValue() const
|
||||
|
||||
QVERIFY(peerVerify->getAttribute("total_seeds").isValid());
|
||||
QVERIFY(peerVerify->getAttribute("total_seeds").isNull());
|
||||
/* SQLite doesn't return correct underlying type in the QVariant for null values
|
||||
like MySQL driver does, skip this compare for the SQLite database. */
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QCOMPARE(peerVerify->getAttribute("total_seeds"), QVariant(QMetaType(QMetaType::Int)));
|
||||
if (DB::connection(connection).driverName() != "QSQLITE")
|
||||
QCOMPARE(peerVerify->getAttribute("total_seeds"),
|
||||
QVariant(QMetaType(QMetaType::Int)));
|
||||
#else
|
||||
QCOMPARE(peerVerify->getAttribute("total_seeds"), QVariant(QVariant::Int));
|
||||
if (DB::connection(connection).driverName() != "QSQLITE")
|
||||
QCOMPARE(peerVerify->getAttribute("total_seeds"), QVariant(QVariant::Int));
|
||||
#endif
|
||||
|
||||
// TODO tests, remove silverqx
|
||||
// Revert
|
||||
peer->setAttribute("total_seeds", 4);
|
||||
peer->save();
|
||||
}
|
||||
|
||||
void tst_BaseModel::save_Update_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto peer = TorrentPeer::find(3);
|
||||
QVERIFY(peer);
|
||||
QVERIFY(peer->exists);
|
||||
@@ -308,6 +333,10 @@ void tst_BaseModel::save_Update_Failed() const
|
||||
|
||||
void tst_BaseModel::remove() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentFile = TorrentPreviewableFile::find(7);
|
||||
|
||||
QVERIFY(torrentFile);
|
||||
@@ -330,6 +359,10 @@ void tst_BaseModel::remove() const
|
||||
|
||||
void tst_BaseModel::destroy() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentFile = TorrentPreviewableFile::find(8);
|
||||
|
||||
QVERIFY(torrentFile);
|
||||
@@ -358,6 +391,10 @@ void tst_BaseModel::destroy() const
|
||||
|
||||
void tst_BaseModel::destroyWithVector() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentFiles =
|
||||
TorrentPreviewableFile::where({{"id", 7, "="},
|
||||
{"id", 8, "=", "or"}})->get();
|
||||
@@ -398,6 +435,10 @@ void tst_BaseModel::destroyWithVector() const
|
||||
|
||||
void tst_BaseModel::all() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrents = Torrent::all();
|
||||
|
||||
QCOMPARE(torrents.size(), 6);
|
||||
@@ -409,6 +450,10 @@ void tst_BaseModel::all() const
|
||||
|
||||
void tst_BaseModel::all_Columns() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrents = Torrent::all();
|
||||
|
||||
@@ -426,6 +471,10 @@ void tst_BaseModel::all_Columns() const
|
||||
|
||||
void tst_BaseModel::latest() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrents = Torrent::latest()->get();
|
||||
const auto createdAtColumn = Torrent::getCreatedAtColumn();
|
||||
|
||||
@@ -442,6 +491,10 @@ void tst_BaseModel::latest() const
|
||||
|
||||
void tst_BaseModel::oldest() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrents = Torrent::oldest()->get();
|
||||
const auto createdAtColumn = Torrent::getCreatedAtColumn();
|
||||
|
||||
@@ -458,6 +511,10 @@ void tst_BaseModel::oldest() const
|
||||
|
||||
void tst_BaseModel::where() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::where("id", "=", 3)->first();
|
||||
QVERIFY(torrent);
|
||||
@@ -475,6 +532,10 @@ void tst_BaseModel::where() const
|
||||
|
||||
void tst_BaseModel::whereEq() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// number
|
||||
{
|
||||
auto torrent = Torrent::whereEq("id", 3)->first();
|
||||
@@ -489,6 +550,10 @@ void tst_BaseModel::whereEq() const
|
||||
}
|
||||
// QDateTime
|
||||
{
|
||||
// CUR tests, sqlite datetime silverqx
|
||||
if (DB::connection(connection).driverName() == "QSQLITE")
|
||||
return;
|
||||
|
||||
auto torrent = Torrent::whereEq(
|
||||
"added_on",
|
||||
QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate))
|
||||
@@ -500,6 +565,10 @@ void tst_BaseModel::whereEq() const
|
||||
|
||||
void tst_BaseModel::where_WithVector() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::where({{"id", 3}})->first();
|
||||
QVERIFY(torrent);
|
||||
@@ -517,6 +586,10 @@ void tst_BaseModel::where_WithVector() const
|
||||
|
||||
void tst_BaseModel::where_WithVector_Condition() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrents = Torrent::where({{"size", 14}, {"progress", 400}})->get();
|
||||
QCOMPARE(torrents.size(), 1);
|
||||
@@ -539,16 +612,29 @@ void tst_BaseModel::where_WithVector_Condition() const
|
||||
|
||||
void tst_BaseModel::arrayOperator() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::where("id", "=", 2)->first();
|
||||
QVERIFY(torrent);
|
||||
QCOMPARE((*torrent)["id"], QVariant(2));
|
||||
QCOMPARE((*torrent)["name"], QVariant("test2"));
|
||||
|
||||
// CUR tests, sqlite datetime silverqx
|
||||
if (DB::connection(connection).driverName() == "QSQLITE")
|
||||
return;
|
||||
|
||||
QCOMPARE((*torrent)["added_on"],
|
||||
QVariant(QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate)));
|
||||
}
|
||||
|
||||
void tst_BaseModel::find() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
QVERIFY(torrent);
|
||||
QCOMPARE(torrent->getAttribute("id"), QVariant(3));
|
||||
@@ -556,6 +642,10 @@ void tst_BaseModel::find() const
|
||||
|
||||
void tst_BaseModel::findOrNew_Found() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::findOrNew(3);
|
||||
|
||||
@@ -576,6 +666,10 @@ void tst_BaseModel::findOrNew_Found() const
|
||||
|
||||
void tst_BaseModel::findOrNew_NotFound() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::findOrNew(999999);
|
||||
|
||||
@@ -596,6 +690,10 @@ void tst_BaseModel::findOrNew_NotFound() const
|
||||
|
||||
void tst_BaseModel::findOrFail_Found() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::findOrFail(3);
|
||||
|
||||
@@ -616,6 +714,10 @@ void tst_BaseModel::findOrFail_Found() const
|
||||
|
||||
void tst_BaseModel::findOrFail_NotFoundFailed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(Torrent::findOrFail(999999),
|
||||
ModelNotFoundError);
|
||||
QVERIFY_EXCEPTION_THROWN(Torrent::findOrFail(999999, {"id", "name"}),
|
||||
@@ -624,6 +726,10 @@ void tst_BaseModel::findOrFail_NotFoundFailed() const
|
||||
|
||||
void tst_BaseModel::firstWhere() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrentFile3 = TorrentPreviewableFile::firstWhere("id", "=", 3);
|
||||
|
||||
@@ -642,6 +748,10 @@ void tst_BaseModel::firstWhere() const
|
||||
|
||||
void tst_BaseModel::firstWhereEq() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentFile3 = TorrentPreviewableFile::firstWhereEq("id", 3);
|
||||
|
||||
QVERIFY(torrentFile3->exists);
|
||||
@@ -651,6 +761,10 @@ void tst_BaseModel::firstWhereEq() const
|
||||
|
||||
void tst_BaseModel::firstOrNew_Found() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::firstOrNew({{"id", 3}});
|
||||
|
||||
@@ -680,6 +794,10 @@ void tst_BaseModel::firstOrNew_Found() const
|
||||
|
||||
void tst_BaseModel::firstOrNew_NotFound() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::firstOrNew({{"id", 100}});
|
||||
|
||||
@@ -707,6 +825,10 @@ void tst_BaseModel::firstOrNew_NotFound() const
|
||||
|
||||
void tst_BaseModel::firstOrCreate_Found() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::firstOrCreate({{"id", 3}});
|
||||
|
||||
@@ -740,6 +862,10 @@ void tst_BaseModel::firstOrCreate_Found() const
|
||||
|
||||
void tst_BaseModel::firstOrCreate_NotFound() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
const auto addedOn = QDateTime::currentDateTime();
|
||||
|
||||
auto torrent = Torrent::firstOrCreate(
|
||||
@@ -767,6 +893,10 @@ void tst_BaseModel::firstOrCreate_NotFound() const
|
||||
|
||||
void tst_BaseModel::isCleanAndIsDirty() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
|
||||
QVERIFY(torrent->isClean());
|
||||
@@ -799,6 +929,10 @@ void tst_BaseModel::isCleanAndIsDirty() const
|
||||
|
||||
void tst_BaseModel::wasChanged() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
|
||||
QVERIFY(!torrent->wasChanged());
|
||||
@@ -822,6 +956,10 @@ void tst_BaseModel::wasChanged() const
|
||||
|
||||
void tst_BaseModel::is() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent2_1 = Torrent::find(2);
|
||||
auto torrent2_2 = Torrent::find(2);
|
||||
|
||||
@@ -831,6 +969,10 @@ void tst_BaseModel::is() const
|
||||
|
||||
void tst_BaseModel::isNot() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent2_1 = Torrent::find(2);
|
||||
auto torrent2_2 = Torrent::find(2);
|
||||
auto torrent3 = Torrent::find(3);
|
||||
@@ -842,12 +984,19 @@ void tst_BaseModel::isNot() const
|
||||
QVERIFY(torrent2_1->isNot(file4));
|
||||
|
||||
// Different connection name
|
||||
torrent2_2->setConnection("crystal");
|
||||
torrent2_2->setConnection("dummy_connection");
|
||||
/* Disable connection override, so isNot() can pickup a connection from the model
|
||||
itself and not overriden connection. */
|
||||
ConnectionOverride::connection = "";
|
||||
QVERIFY(torrent2_1->isNot(torrent2_2));
|
||||
}
|
||||
|
||||
void tst_BaseModel::fresh() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// Doesn't exist
|
||||
{
|
||||
Torrent torrent;
|
||||
@@ -875,6 +1024,10 @@ void tst_BaseModel::fresh() const
|
||||
|
||||
void tst_BaseModel::refresh_OnlyAttributes() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// Doens't exist
|
||||
{
|
||||
Torrent torrent;
|
||||
@@ -905,6 +1058,10 @@ void tst_BaseModel::refresh_OnlyAttributes() const
|
||||
|
||||
void tst_BaseModel::create() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto addedOn = QDateTime::fromString("2021-02-01 20:22:10", Qt::ISODate);
|
||||
|
||||
auto torrent = Torrent::create({
|
||||
@@ -942,6 +1099,10 @@ void tst_BaseModel::create() const
|
||||
|
||||
void tst_BaseModel::create_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto addedOn = QDateTime::fromString("2021-02-01 20:22:10", Qt::ISODate);
|
||||
|
||||
Torrent torrent;
|
||||
@@ -959,6 +1120,14 @@ void tst_BaseModel::create_Failed() const
|
||||
|
||||
void tst_BaseModel::update() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// CUR tests, sqlite datetime silverqx
|
||||
if (DB::connection(connection).driverName() == "QSQLITE")
|
||||
QSKIP("QSQLITE doesn't return QDateTime QVariant, but QString.", );
|
||||
|
||||
auto timeBeforeUpdate = QDateTime::currentDateTime();
|
||||
// Reset milliseconds to 0
|
||||
{
|
||||
@@ -1005,6 +1174,10 @@ void tst_BaseModel::update() const
|
||||
|
||||
void tst_BaseModel::update_NonExistent() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Torrent torrent;
|
||||
|
||||
auto result = torrent.update({{"progress", 333}});
|
||||
@@ -1013,6 +1186,10 @@ void tst_BaseModel::update_NonExistent() const
|
||||
|
||||
void tst_BaseModel::update_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -1025,6 +1202,10 @@ void tst_BaseModel::update_Failed() const
|
||||
|
||||
void tst_BaseModel::update_SameValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
QVERIFY(torrent->exists);
|
||||
|
||||
@@ -1045,6 +1226,35 @@ void tst_BaseModel::update_SameValue() const
|
||||
QCOMPARE(torrentVerify->getAttribute(updatedAtColumn), updatedAt);
|
||||
}
|
||||
|
||||
void tst_BaseModel::truncate() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Setting setting;
|
||||
setting.setAttribute("name", "truncate");
|
||||
setting.setAttribute("value", "yes");
|
||||
|
||||
QVERIFY(!setting.exists);
|
||||
const auto result = setting.save();
|
||||
QVERIFY(result);
|
||||
QVERIFY(setting.exists);
|
||||
|
||||
// Get the fresh record from the database
|
||||
auto settingToVerify = Setting::whereEq("name", "truncate")->first();
|
||||
QVERIFY(settingToVerify);
|
||||
QVERIFY(settingToVerify->exists);
|
||||
|
||||
// And check attributes
|
||||
QCOMPARE(settingToVerify->getAttribute("name"), QVariant("truncate"));
|
||||
QCOMPARE(settingToVerify->getAttribute("value"), QVariant("yes"));
|
||||
|
||||
Setting::truncate();
|
||||
|
||||
QCOMPARE(Setting::all().size(), 0);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_BaseModel)
|
||||
|
||||
#include "tst_basemodel.moc"
|
||||
|
||||
@@ -10,12 +10,9 @@
|
||||
|
||||
#include "database.hpp"
|
||||
|
||||
// TODO tests, namespace silverqx
|
||||
//using namespace Orm::Tiny;
|
||||
|
||||
using Orm::ConnectionInterface;
|
||||
using Orm::One;
|
||||
using Orm::OrmRuntimeError;
|
||||
using Orm::Tiny::ConnectionOverride;
|
||||
using Orm::Tiny::RelationNotFoundError;
|
||||
using Orm::Tiny::RelationNotLoadedError;
|
||||
|
||||
@@ -23,13 +20,8 @@ class tst_BaseModel_Relations : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_BaseModel_Relations();
|
||||
~tst_BaseModel_Relations() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void getRelation_EagerLoad_ManyAndOne() const;
|
||||
void getRelation_EagerLoad_BelongsTo() const;
|
||||
@@ -64,24 +56,23 @@ private slots:
|
||||
|
||||
void where_WithCallback() const;
|
||||
void orWhere_WithCallback() const;
|
||||
|
||||
private:
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
};
|
||||
|
||||
tst_BaseModel_Relations::tst_BaseModel_Relations()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
void tst_BaseModel_Relations::initTestCase_data() const
|
||||
{
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
void tst_BaseModel_Relations::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_BaseModel_Relations::cleanupTestCase()
|
||||
{}
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_BaseModel_Relations::getRelation_EagerLoad_ManyAndOne() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -118,6 +109,10 @@ void tst_BaseModel_Relations::getRelation_EagerLoad_ManyAndOne() const
|
||||
|
||||
void tst_BaseModel_Relations::getRelation_EagerLoad_BelongsTo() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentPeer = TorrentPeerEager::find(2);
|
||||
QVERIFY(torrentPeer);
|
||||
QVERIFY(torrentPeer->exists);
|
||||
@@ -132,6 +127,10 @@ void tst_BaseModel_Relations::getRelation_EagerLoad_BelongsTo() const
|
||||
|
||||
void tst_BaseModel_Relations::getRelation_EagerLoad_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Torrent torrent;
|
||||
|
||||
// Many relation
|
||||
@@ -154,12 +153,20 @@ void tst_BaseModel_Relations::getRelation_EagerLoad_Failed() const
|
||||
|
||||
void tst_BaseModel_Relations::EagerLoad_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(TorrentEager_Failed::find(1),
|
||||
RelationNotFoundError);
|
||||
}
|
||||
|
||||
void tst_BaseModel_Relations::getRelationValue_LazyLoad_ManyAndOne() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -196,6 +203,10 @@ void tst_BaseModel_Relations::getRelationValue_LazyLoad_ManyAndOne() const
|
||||
|
||||
void tst_BaseModel_Relations::getRelationValue_LazyLoad_BelongsTo() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrentPeer = TorrentPeer::find(2);
|
||||
QVERIFY(torrentPeer);
|
||||
QVERIFY(torrentPeer->exists);
|
||||
@@ -210,6 +221,10 @@ void tst_BaseModel_Relations::getRelationValue_LazyLoad_BelongsTo() const
|
||||
|
||||
void tst_BaseModel_Relations::getRelationValue_LazyLoad_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// Many relation
|
||||
QCOMPARE((Torrent().getRelationValue<TorrentPreviewableFile>("notExists")),
|
||||
QVector<TorrentPreviewableFile *>());
|
||||
@@ -223,6 +238,10 @@ void tst_BaseModel_Relations::getRelationValue_LazyLoad_Failed() const
|
||||
|
||||
void tst_BaseModel_Relations::u_with_Empty() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
Torrent torrent;
|
||||
|
||||
QCOMPARE(torrent.getRelations().size(), 0);
|
||||
@@ -230,6 +249,10 @@ void tst_BaseModel_Relations::u_with_Empty() const
|
||||
|
||||
void tst_BaseModel_Relations::with_HasOne() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::with("torrentPeer")->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -244,6 +267,10 @@ void tst_BaseModel_Relations::with_HasOne() const
|
||||
|
||||
void tst_BaseModel_Relations::with_HasMany() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::with("torrentFiles")->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -265,6 +292,10 @@ void tst_BaseModel_Relations::with_HasMany() const
|
||||
|
||||
void tst_BaseModel_Relations::with_BelongsTo() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto fileProperty = TorrentPreviewableFileProperty::with("torrentFile")->find(2);
|
||||
QVERIFY(fileProperty);
|
||||
QVERIFY(fileProperty->exists);
|
||||
@@ -279,6 +310,10 @@ void tst_BaseModel_Relations::with_BelongsTo() const
|
||||
|
||||
void tst_BaseModel_Relations::with_NestedRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::with("torrentFiles.fileProperty")->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -315,6 +350,10 @@ void tst_BaseModel_Relations::with_NestedRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::with_Vector_MoreRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::with({{"torrentFiles"}, {"torrentPeer"}})->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -350,12 +389,20 @@ void tst_BaseModel_Relations::with_Vector_MoreRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::with_NonExistentRelation_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(Torrent::with("torrentFiles-NON_EXISTENT")->find(1),
|
||||
RelationNotFoundError);
|
||||
}
|
||||
|
||||
void tst_BaseModel_Relations::without() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::without("torrentPeer")->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -368,6 +415,10 @@ void tst_BaseModel_Relations::without() const
|
||||
|
||||
void tst_BaseModel_Relations::without_NestedRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::without("torrentFiles")->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -380,6 +431,10 @@ void tst_BaseModel_Relations::without_NestedRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::without_Vector_MoreRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::without({"torrentPeer", "torrentFiles"})->find(2);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -389,6 +444,10 @@ void tst_BaseModel_Relations::without_Vector_MoreRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::load() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(2);
|
||||
|
||||
QVERIFY(torrent->getRelations().empty());
|
||||
@@ -432,6 +491,10 @@ void tst_BaseModel_Relations::load() const
|
||||
|
||||
void tst_BaseModel_Relations::load_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(2);
|
||||
|
||||
QVERIFY(torrent->getRelations().empty());
|
||||
@@ -443,6 +506,10 @@ void tst_BaseModel_Relations::load_Failed() const
|
||||
|
||||
void tst_BaseModel_Relations::refresh_EagerLoad_OnlyRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::find(3);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -514,6 +581,10 @@ void tst_BaseModel_Relations::refresh_EagerLoad_OnlyRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::refresh_LazyLoad_OnlyRelations() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(3);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -586,6 +657,10 @@ void tst_BaseModel_Relations::refresh_LazyLoad_OnlyRelations() const
|
||||
|
||||
void tst_BaseModel_Relations::push_EagerLoad() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = TorrentEager::find(2);
|
||||
|
||||
QVERIFY(torrent);
|
||||
@@ -658,6 +733,10 @@ void tst_BaseModel_Relations::push_EagerLoad() const
|
||||
|
||||
void tst_BaseModel_Relations::push_LazyLoad() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(2);
|
||||
|
||||
QVERIFY(torrent);
|
||||
@@ -728,6 +807,10 @@ void tst_BaseModel_Relations::push_LazyLoad() const
|
||||
|
||||
void tst_BaseModel_Relations::where_WithCallback() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto files = Torrent::find(5)->torrentFiles()
|
||||
->where([](auto &query)
|
||||
{
|
||||
@@ -749,6 +832,10 @@ void tst_BaseModel_Relations::where_WithCallback() const
|
||||
|
||||
void tst_BaseModel_Relations::orWhere_WithCallback() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto files = Torrent::find(5)->torrentFiles()
|
||||
->where("progress", ">", 990)
|
||||
.orWhere([](auto &query)
|
||||
|
||||
@@ -10,6 +10,8 @@ HEADERS += \
|
||||
$$PWD/models/filepropertyproperty.hpp \
|
||||
$$PWD/models/forwards.hpp \
|
||||
$$PWD/models/forwardseager.hpp \
|
||||
$$PWD/models/role.hpp \
|
||||
$$PWD/models/roleuser.hpp \
|
||||
$$PWD/models/setting.hpp \
|
||||
$$PWD/models/tag.hpp \
|
||||
$$PWD/models/tagged.hpp \
|
||||
@@ -24,3 +26,4 @@ HEADERS += \
|
||||
$$PWD/models/torrentpreviewablefileeager.hpp \
|
||||
$$PWD/models/torrentpreviewablefileproperty.hpp \
|
||||
$$PWD/models/torrentpreviewablefilepropertyeager.hpp \
|
||||
$$PWD/models/user.hpp \
|
||||
|
||||
@@ -41,11 +41,6 @@ private:
|
||||
// {"fileProperty"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
|
||||
/*! All of the relationships to be touched. */
|
||||
QStringList u_touches {"fileProperty"};
|
||||
};
|
||||
|
||||
@@ -16,11 +16,6 @@ private:
|
||||
|
||||
/*! Indicates if the model's ID is auto-incrementing. */
|
||||
bool u_incrementing = false;
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // SETTING_H
|
||||
|
||||
@@ -67,11 +67,6 @@ private:
|
||||
{"tagProperty"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
|
||||
/*! All of the relationships to be touched. */
|
||||
QStringList u_touches {"torrents"};
|
||||
};
|
||||
|
||||
@@ -12,11 +12,6 @@ class TagProperty final : public BaseModel<TagProperty>
|
||||
|
||||
/*! The table associated with the model. */
|
||||
QString u_table {"tag_properties"};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TAGPROPERTY_H
|
||||
|
||||
@@ -115,13 +115,14 @@ private:
|
||||
// {"tags"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_PLAYGROUND
|
||||
/*! The connection name for the model. */
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#else
|
||||
QString u_connection {"crystal"};
|
||||
QString u_connection {"mysql_alt"};
|
||||
#endif
|
||||
|
||||
/*! The connection name for the model. */
|
||||
// QString u_connection {"sqlite"};
|
||||
|
||||
/*! Indicates if the model should be timestamped. */
|
||||
// bool u_timestamps = true;
|
||||
/*! The storage format of the model's date columns. */
|
||||
|
||||
@@ -58,11 +58,6 @@ private:
|
||||
{"torrentFiles.fileProperty"},
|
||||
{"torrentPeer"},
|
||||
};
|
||||
|
||||
/*! The connection name for the model. */
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTEAGER_H
|
||||
|
||||
@@ -45,11 +45,6 @@ private:
|
||||
QVector<WithItem> u_with {
|
||||
{"torrentFiles-NON_EXISTENT"},
|
||||
};
|
||||
|
||||
/*! The connection name for the model. */
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTEAGER_FAILED_H
|
||||
|
||||
@@ -38,11 +38,6 @@ private:
|
||||
QVector<WithItem> u_with {
|
||||
// {"torrent"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTPEER_H
|
||||
|
||||
@@ -38,11 +38,6 @@ private:
|
||||
QVector<WithItem> u_with {
|
||||
{"torrent"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTPEEREAGER_H
|
||||
|
||||
@@ -12,11 +12,6 @@ class TorrentPeerEager_NoRelations final : public BaseModel<TorrentPeerEager_NoR
|
||||
|
||||
/*! The table associated with the model. */
|
||||
QString u_table {"torrent_peers"};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTPEEREAGER_NORELATIONS_H
|
||||
|
||||
@@ -53,11 +53,6 @@ private:
|
||||
// {"fileProperty"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
|
||||
/*! All of the relationships to be touched. */
|
||||
QStringList u_touches {"torrent"};
|
||||
};
|
||||
|
||||
@@ -44,11 +44,6 @@ private:
|
||||
QVector<WithItem> u_with {
|
||||
{"fileProperty"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // TORRENTPREVIEWABLEFILEEAGER_H
|
||||
|
||||
@@ -40,11 +40,6 @@ private:
|
||||
// {"torrentFile"},
|
||||
};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
|
||||
/*! Indicates if the model should be timestamped. */
|
||||
bool u_timestamps = false;
|
||||
|
||||
|
||||
@@ -14,11 +14,6 @@ class TorrentPreviewableFilePropertyEager final :
|
||||
/*! The table associated with the model. */
|
||||
QString u_table {"torrent_previewable_file_properties"};
|
||||
|
||||
#ifdef PROJECT_TINYORM_TEST
|
||||
/*! The connection name for the model. */
|
||||
QString u_connection {"tinyorm_mysql_tests"};
|
||||
#endif
|
||||
|
||||
/*! Indicates if the model should be timestamped. */
|
||||
bool u_timestamps = false;
|
||||
};
|
||||
|
||||
@@ -7,20 +7,15 @@
|
||||
#include "database.hpp"
|
||||
|
||||
using Orm::AttributeItem;
|
||||
using Orm::ConnectionInterface;
|
||||
using Orm::QueryError;
|
||||
using Orm::Tiny::ConnectionOverride;
|
||||
|
||||
class tst_Relations_Inserting_Updating : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_Relations_Inserting_Updating();
|
||||
~tst_Relations_Inserting_Updating() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void save_OnHasOneOrMany() const;
|
||||
void save_OnHasOneOrMany_WithRValue() const;
|
||||
@@ -39,24 +34,23 @@ private slots:
|
||||
void createMany_OnHasOneOrMany_WithRValue() const;
|
||||
void createMany_OnHasOneOrMany_Failed() const;
|
||||
void createMany_OnHasOneOrMany_WithRValue_Failed() const;
|
||||
|
||||
private:
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
};
|
||||
|
||||
tst_Relations_Inserting_Updating::tst_Relations_Inserting_Updating()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
void tst_Relations_Inserting_Updating::initTestCase_data() const
|
||||
{
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
void tst_Relations_Inserting_Updating::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_Relations_Inserting_Updating::cleanupTestCase()
|
||||
{}
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_Relations_Inserting_Updating::save_OnHasOneOrMany() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -97,6 +91,10 @@ void tst_Relations_Inserting_Updating::save_OnHasOneOrMany() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::save_OnHasOneOrMany_WithRValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -130,6 +128,10 @@ void tst_Relations_Inserting_Updating::save_OnHasOneOrMany_WithRValue() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::save_OnHasOneOrMany_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -152,6 +154,10 @@ void tst_Relations_Inserting_Updating::save_OnHasOneOrMany_Failed() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -220,6 +226,10 @@ void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany_WithRValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -275,6 +285,10 @@ void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany_WithRValue() cons
|
||||
|
||||
void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -300,6 +314,10 @@ void tst_Relations_Inserting_Updating::saveMany_OnHasOneOrMany_Failed() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::create_OnHasOneOrMany() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -334,6 +352,10 @@ void tst_Relations_Inserting_Updating::create_OnHasOneOrMany() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_WithRValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -366,6 +388,10 @@ void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_WithRValue() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -387,6 +413,10 @@ void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_Failed() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_WithRValue_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -407,6 +437,10 @@ void tst_Relations_Inserting_Updating::create_OnHasOneOrMany_WithRValue_Failed()
|
||||
|
||||
void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -470,6 +504,10 @@ void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany() const
|
||||
|
||||
void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany_WithRValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(5);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -525,6 +563,10 @@ void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany_WithRValue() co
|
||||
|
||||
void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
@@ -554,6 +596,10 @@ void tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany_Failed() const
|
||||
void
|
||||
tst_Relations_Inserting_Updating::createMany_OnHasOneOrMany_WithRValue_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrent = Torrent::find(1);
|
||||
QVERIFY(torrent);
|
||||
QVERIFY(torrent->exists);
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QtTest>
|
||||
|
||||
#include "orm/db.hpp"
|
||||
|
||||
#include "models/torrent.hpp"
|
||||
|
||||
#include "database.hpp"
|
||||
|
||||
using namespace Orm;
|
||||
// TODO tests, namespace silverqx
|
||||
using namespace Orm::Tiny;
|
||||
using Orm::QueryError;
|
||||
using Orm::Tiny::ConnectionOverride;
|
||||
using Orm::Tiny::ModelNotFoundError;
|
||||
|
||||
template<typename Model>
|
||||
using TinyBuilder = Orm::Tiny::Builder<Model>;
|
||||
|
||||
class tst_TinyBuilder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_TinyBuilder();
|
||||
~tst_TinyBuilder() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void get() const;
|
||||
void get_Columns() const;
|
||||
@@ -42,23 +42,23 @@ private:
|
||||
inline static std::unique_ptr<TinyBuilder<Model>>
|
||||
createQuery()
|
||||
{ return Model().query(); }
|
||||
|
||||
/*! The database connection instance. */
|
||||
ConnectionInterface &m_connection;
|
||||
};
|
||||
|
||||
tst_TinyBuilder::tst_TinyBuilder()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
void tst_TinyBuilder::initTestCase_data() const
|
||||
{
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
void tst_TinyBuilder::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_TinyBuilder::cleanupTestCase()
|
||||
{}
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_TinyBuilder::get() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrents = createQuery<Torrent>()->get();
|
||||
|
||||
QCOMPARE(torrents.size(), 6);
|
||||
@@ -72,17 +72,25 @@ void tst_TinyBuilder::get() const
|
||||
|
||||
void tst_TinyBuilder::get_Columns() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto torrents = createQuery<Torrent>()->get({"id", "name", "size"});
|
||||
|
||||
const auto &torrent3 = torrents.at(1);
|
||||
QCOMPARE(torrent3.getAttributes().size(), 3);
|
||||
QCOMPARE(torrent3.getAttributes().at(0).key, QString("id"));
|
||||
QCOMPARE(torrent3.getAttributes().at(1).key, QString("name"));
|
||||
QCOMPARE(torrent3.getAttributes().at(2).key, QString("size"));
|
||||
const auto &torrent = torrents.at(1);
|
||||
QCOMPARE(torrent.getAttributes().size(), 3);
|
||||
QCOMPARE(torrent.getAttributes().at(0).key, QString("id"));
|
||||
QCOMPARE(torrent.getAttributes().at(1).key, QString("name"));
|
||||
QCOMPARE(torrent.getAttributes().at(2).key, QString("size"));
|
||||
}
|
||||
|
||||
void tst_TinyBuilder::value() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto value = Torrent::whereEq("id", 2)->value("name");
|
||||
|
||||
QCOMPARE(value, QVariant("test2"));
|
||||
@@ -90,6 +98,10 @@ void tst_TinyBuilder::value() const
|
||||
|
||||
void tst_TinyBuilder::value_ModelNotFound() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto value = Torrent::whereEq("id", 999999)->value("name");
|
||||
|
||||
QVERIFY(!value.isValid());
|
||||
@@ -98,6 +110,10 @@ void tst_TinyBuilder::value_ModelNotFound() const
|
||||
|
||||
void tst_TinyBuilder::firstOrFail_Found() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
{
|
||||
auto torrent = Torrent::whereEq("id", 3)->firstOrFail();
|
||||
|
||||
@@ -118,6 +134,10 @@ void tst_TinyBuilder::firstOrFail_Found() const
|
||||
|
||||
void tst_TinyBuilder::firstOrFail_NotFoundFailed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(Torrent::whereEq("id", 999999)->firstOrFail(),
|
||||
ModelNotFoundError);
|
||||
QVERIFY_EXCEPTION_THROWN(Torrent::whereEq("id", 999999)->firstOrFail({"id", "name"}),
|
||||
@@ -126,6 +146,14 @@ void tst_TinyBuilder::firstOrFail_NotFoundFailed() const
|
||||
|
||||
void tst_TinyBuilder::incrementAndDecrement() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// CUR tests, sqlite datetime silverqx
|
||||
if (DB::connection(connection).driverName() == "QSQLITE")
|
||||
QSKIP("QSQLITE doesn't return QDateTime QVariant, but QString.", );
|
||||
|
||||
auto timeBeforeIncrement = QDateTime::currentDateTime();
|
||||
// Reset milliseconds to 0
|
||||
{
|
||||
@@ -173,6 +201,14 @@ void tst_TinyBuilder::incrementAndDecrement() const
|
||||
|
||||
void tst_TinyBuilder::update() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
// CUR tests, sqlite datetime silverqx
|
||||
if (DB::connection(connection).driverName() == "QSQLITE")
|
||||
QSKIP("QSQLITE doesn't return QDateTime QVariant, but QString.", );
|
||||
|
||||
auto timeBeforeUpdate = QDateTime::currentDateTime();
|
||||
// Reset milliseconds to 0
|
||||
{
|
||||
@@ -220,6 +256,10 @@ void tst_TinyBuilder::update() const
|
||||
|
||||
void tst_TinyBuilder::update_Failed() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
QVERIFY_EXCEPTION_THROWN(
|
||||
Torrent::whereEq("id", 3)->update({{"progress-NON_EXISTENT", 333}}),
|
||||
QueryError);
|
||||
@@ -227,6 +267,10 @@ void tst_TinyBuilder::update_Failed() const
|
||||
|
||||
void tst_TinyBuilder::update_SameValue() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
ConnectionOverride::connection = connection;
|
||||
|
||||
auto timeBeforeUpdate = QDateTime::currentDateTime();
|
||||
// Reset milliseconds to 0
|
||||
{
|
||||
|
||||
@@ -1,45 +1,71 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QtTest>
|
||||
|
||||
#include "orm/db.hpp"
|
||||
#include "orm/mysqlconnection.hpp"
|
||||
|
||||
#include "database.hpp"
|
||||
|
||||
using namespace Orm;
|
||||
using Orm::MySqlConnection;
|
||||
|
||||
class tst_DatabaseConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_DatabaseConnection();
|
||||
~tst_DatabaseConnection() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void initTestCase_data() const;
|
||||
|
||||
void pingDatabase();
|
||||
void pingDatabase() const;
|
||||
|
||||
private:
|
||||
ConnectionInterface &m_connection;
|
||||
void isNotMaria_OnMySqlConnection() const;
|
||||
};
|
||||
|
||||
tst_DatabaseConnection::tst_DatabaseConnection()
|
||||
: m_connection(TestUtils::Database::createConnection())
|
||||
{}
|
||||
|
||||
void tst_DatabaseConnection::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_DatabaseConnection::cleanupTestCase()
|
||||
{}
|
||||
|
||||
void tst_DatabaseConnection::pingDatabase()
|
||||
void tst_DatabaseConnection::initTestCase_data() const
|
||||
{
|
||||
const auto result = m_connection.pingDatabase();
|
||||
QTest::addColumn<QString>("connection");
|
||||
|
||||
// Run all tests for all supported database connections
|
||||
for (const auto &connection : TestUtils::Database::createConnections())
|
||||
QTest::newRow(connection.toUtf8().constData()) << connection;
|
||||
}
|
||||
|
||||
void tst_DatabaseConnection::pingDatabase() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
auto &connection_ = DB::connection(connection);
|
||||
|
||||
if (const auto driverName = connection_.driverName();
|
||||
driverName == "QSQLITE"
|
||||
)
|
||||
QSKIP(QStringLiteral("The '%1' database driver doesn't support ping command.")
|
||||
.arg(driverName).toUtf8().constData(), );
|
||||
|
||||
const auto result = connection_.pingDatabase();
|
||||
|
||||
QVERIFY2(result, "Ping database failed.");
|
||||
}
|
||||
|
||||
void tst_DatabaseConnection::isNotMaria_OnMySqlConnection() const
|
||||
{
|
||||
QFETCH_GLOBAL(QString, connection);
|
||||
|
||||
auto &connection_ = DB::connection(connection);
|
||||
|
||||
if (const auto driverName = connection_.driverName();
|
||||
driverName != "QMYSQL"
|
||||
)
|
||||
QSKIP(QStringLiteral("The '%1' database driver doesn't implement isMaria() "
|
||||
"method.")
|
||||
.arg(driverName).toUtf8().constData(), );
|
||||
|
||||
const auto expected = !(connection == "tinyorm_mysql_tests");
|
||||
|
||||
const auto isMaria = dynamic_cast<MySqlConnection &>(connection_).isMaria();
|
||||
|
||||
QCOMPARE(isMaria, expected);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_DatabaseConnection)
|
||||
|
||||
#include "tst_databaseconnection.moc"
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QtTest>
|
||||
|
||||
#include "orm/grammar.hpp"
|
||||
|
||||
class tst_Grammar : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
tst_Grammar();
|
||||
~tst_Grammar() = default;
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
void test_case1();
|
||||
|
||||
void test_case1() const;
|
||||
};
|
||||
|
||||
tst_Grammar::tst_Grammar()
|
||||
{}
|
||||
|
||||
void tst_Grammar::initTestCase()
|
||||
{}
|
||||
|
||||
void tst_Grammar::cleanupTestCase()
|
||||
{}
|
||||
|
||||
void tst_Grammar::test_case1()
|
||||
void tst_Grammar::test_case1() const
|
||||
{}
|
||||
|
||||
QTEST_MAIN(tst_Grammar)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,35 +5,49 @@
|
||||
namespace TestUtils
|
||||
{
|
||||
|
||||
Database::Database()
|
||||
{}
|
||||
|
||||
Orm::ConnectionInterface &Database::createConnection()
|
||||
const QStringList &Database::createConnections()
|
||||
{
|
||||
static const auto connectionName = QStringLiteral("tinyorm_mysql_tests");
|
||||
// Ownership of a unique_ptr()
|
||||
static const auto manager = DB::create({
|
||||
{"tinyorm_mysql_tests", {
|
||||
{"driver", "QMYSQL"},
|
||||
{"host", qEnvironmentVariable("DB_MYSQL_HOST", "127.0.0.1")},
|
||||
{"port", qEnvironmentVariable("DB_MYSQL_PORT", "3306")},
|
||||
{"database", qEnvironmentVariable("DB_MYSQL_DATABASE", "")},
|
||||
{"username", qEnvironmentVariable("DB_MYSQL_USERNAME", "root")},
|
||||
{"password", qEnvironmentVariable("DB_MYSQL_PASSWORD", "")},
|
||||
{"charset", qEnvironmentVariable("DB_MYSQL_CHARSET", "utf8mb4")},
|
||||
{"collation", qEnvironmentVariable("DB_MYSQL_COLLATION",
|
||||
"utf8mb4_0900_ai_ci")},
|
||||
// Very important for tests
|
||||
{"timezone", "+00:00"},
|
||||
{"prefix", ""},
|
||||
{"strict", true},
|
||||
{"options", QVariantHash()},
|
||||
// TODO future remove, when unit tested silverqx
|
||||
// Example
|
||||
// {"options", "MYSQL_OPT_CONNECT_TIMEOUT = 5 ; MYSQL_OPT_RECONNECT=1"},
|
||||
// {"options", QVariantHash {{"MYSQL_OPT_RECONNECT", 1},
|
||||
// {"MYSQL_OPT_READ_TIMEOUT", 10}}},
|
||||
}},
|
||||
|
||||
static auto manager = DB::create({
|
||||
{"driver", "QMYSQL"},
|
||||
{"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", "")},
|
||||
{"charset", qEnvironmentVariable("DB_CHARSET", "utf8mb4")},
|
||||
{"collation", qEnvironmentVariable("DB_COLLATION", "utf8mb4_unicode_ci")},
|
||||
{"prefix", ""},
|
||||
{"strict", true},
|
||||
{"options", QVariantHash()},
|
||||
// TODO future remove, when unit tested silverqx
|
||||
// Example
|
||||
// {"options", "MYSQL_OPT_CONNECT_TIMEOUT = 5 ; MYSQL_OPT_RECONNECT=1"},
|
||||
// {"options", QVariantHash {{"MYSQL_OPT_RECONNECT", 1},
|
||||
// {"MYSQL_OPT_READ_TIMEOUT", 10}}},
|
||||
}, connectionName);
|
||||
{"tinyorm_sqlite_tests", {
|
||||
{"driver", "QSQLITE"},
|
||||
{"database", qEnvironmentVariable("DB_SQLITE_DATABASE",
|
||||
TINYORM_SQLITE_DATABASE)},
|
||||
{"foreign_key_constraints", qEnvironmentVariable("DB_SQLITE_FOREIGN_KEYS",
|
||||
"true")},
|
||||
{"check_database_exists", true},
|
||||
}},
|
||||
},
|
||||
/* The default connection is empty for tests, there is no default connection
|
||||
because it can produce hard to find bugs, I have to be explicit about
|
||||
the connection which will be used. */
|
||||
"");
|
||||
|
||||
static auto &connection = manager->connection(connectionName);
|
||||
static const auto cachedConnectionNames = manager->connectionNames();
|
||||
|
||||
return connection;
|
||||
return cachedConnectionNames;
|
||||
}
|
||||
|
||||
} // namespace TestUtils
|
||||
|
||||
@@ -10,10 +10,11 @@ namespace TestUtils
|
||||
|
||||
class UTILS_EXPORT Database
|
||||
{
|
||||
public:
|
||||
Database();
|
||||
Q_DISABLE_COPY(Database)
|
||||
|
||||
static Orm::ConnectionInterface &createConnection();
|
||||
public:
|
||||
/*! Create all database connections which will be tested. */
|
||||
static const QStringList &createConnections();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
5
tests/auto/utils/testdata/composer.json
vendored
Normal file
5
tests/auto/utils/testdata/composer.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"require": {
|
||||
"illuminate/database": "^8.33"
|
||||
}
|
||||
}
|
||||
1631
tests/auto/utils/testdata/composer.lock
generated
vendored
Normal file
1631
tests/auto/utils/testdata/composer.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
338
tests/auto/utils/testdata/create_and_seed_database.php
vendored
Normal file
338
tests/auto/utils/testdata/create_and_seed_database.php
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
use Illuminate\Database\Capsule\Manager as Capsule;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
|
||||
/**
|
||||
* Combine Insert statement values with columns.
|
||||
*
|
||||
* @param array $columns
|
||||
* @param array $values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function combineValues(array $columns, array $values): array
|
||||
{
|
||||
$result = [];
|
||||
$columnsSize = count($columns);
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (count($value) != $columnsSize)
|
||||
throw new InvalidArgumentException(
|
||||
'\'$columns\' and \'$values\' in the parseInsertValues() have to have ' .
|
||||
'the same elements size.');
|
||||
|
||||
$result[] = array_combine($columns, $value);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop all tables for the given connection.
|
||||
*
|
||||
* @param string $connection The connection name
|
||||
* @return void
|
||||
*/
|
||||
function dropAllTables(string $connection)
|
||||
{
|
||||
Capsule::schema($connection)->dropAllTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add all configuration connections to the capsule.
|
||||
*
|
||||
* @param Capsule $capsule
|
||||
* @param array $configs
|
||||
*/
|
||||
function addConnections(Capsule $capsule, array $configs = [])
|
||||
{
|
||||
foreach ($configs as $name => $config)
|
||||
$capsule->addConnection($config, $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all tables for the given connection.
|
||||
*
|
||||
* @param string $connection The connection name
|
||||
* @return void
|
||||
*/
|
||||
function createTables(string $connection)
|
||||
{
|
||||
$schema = Capsule::schema($connection);
|
||||
|
||||
$schema->create('settings', function (Blueprint $table) {
|
||||
$table->string('name')->default('')->unique();
|
||||
$table->string('value')->default('');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$schema->create('torrents', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique()->comment('Torrent name');
|
||||
$table->unsignedBigInteger('size')->default('0');
|
||||
$table->unsignedSmallInteger('progress')->default('0');
|
||||
$table->dateTime('added_on')->useCurrent();
|
||||
$table->string('hash', 40);
|
||||
$table->string('note')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$schema->create('torrent_peers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('torrent_id');
|
||||
$table->integer('seeds')->nullable();
|
||||
$table->integer('total_seeds')->nullable();
|
||||
$table->integer('leechers');
|
||||
$table->integer('total_leechers');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('torrent_id')->references('id')->on('torrents')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('torrent_previewable_files', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('torrent_id');
|
||||
$table->integer('file_index');
|
||||
$table->string('filepath')->unique();
|
||||
$table->unsignedBigInteger('size');
|
||||
$table->unsignedSmallInteger('progress');
|
||||
$table->string('note')->nullable();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('torrent_id')->references('id')->on('torrents')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('torrent_previewable_file_properties', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('previewable_file_id');
|
||||
$table->string('name')->unique();
|
||||
$table->unsignedBigInteger('size');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('previewable_file_id')->references('id')
|
||||
->on('torrent_previewable_files')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('file_property_properties', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('file_property_id');
|
||||
$table->string('name')->unique();
|
||||
$table->unsignedBigInteger('value');
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('file_property_id')->references('id')
|
||||
->on('torrent_previewable_file_properties')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('torrent_tags', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
$schema->create('tag_torrent', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('torrent_id');
|
||||
$table->unsignedBigInteger('tag_id');
|
||||
$table->boolean('active')->default(1);
|
||||
$table->timestamps();
|
||||
|
||||
$table->primary(['torrent_id', 'tag_id']);
|
||||
|
||||
$table->foreign('torrent_id')->references('id')->on('torrents')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->foreign('tag_id')->references('id')->on('torrent_tags')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('tag_properties', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedBigInteger('tag_id');
|
||||
$table->string('color');
|
||||
$table->unsignedInteger('position')->unique();
|
||||
$table->timestamps();
|
||||
|
||||
$table->foreign('tag_id')->references('id')->on('torrent_tags')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
|
||||
$schema->create('users', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
});
|
||||
|
||||
$schema->create('roles', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('name')->unique();
|
||||
});
|
||||
|
||||
$schema->create('role_user', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('role_id');
|
||||
$table->unsignedBigInteger('user_id');
|
||||
$table->boolean('active')->default(1);
|
||||
|
||||
$table->primary(['role_id', 'user_id']);
|
||||
|
||||
$table->foreign('role_id')->references('id')->on('roles')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->foreign('user_id')->references('id')->on('users')
|
||||
->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed all tables with data.
|
||||
*
|
||||
* @param string $connection The connection name
|
||||
* @return void
|
||||
*/
|
||||
function seedTables(string $connection)
|
||||
{
|
||||
Capsule::table('torrents', null, $connection)->insert(
|
||||
combineValues(['id', 'name', 'size', 'progress', 'added_on', 'hash', 'note', 'created_at', 'updated_at'], [
|
||||
[1, 'test1', 11, 100, '2020-08-01 20:11:10', '1579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-01 14:51:23', '2021-01-01 18:46:31'],
|
||||
[2, 'test2', 12, 200, '2020-08-02 20:11:10', '2579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-02 14:51:23', '2021-01-02 18:46:31'],
|
||||
[3, 'test3', 13, 300, '2020-08-03 20:11:10', '3579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-03 14:51:23', '2021-01-03 18:46:31'],
|
||||
[4, 'test4', 14, 400, '2020-08-04 20:11:10', '4579e3af2768cdf52ec84c1f320333f68401dc6e', 'after update revert updated_at', '2021-01-04 14:51:23', '2021-01-04 18:46:31'],
|
||||
[5, 'test5', 15, 500, '2020-08-05 20:11:10', '5579e3af2768cdf52ec84c1f320333f68401dc6e', 'no peers', '2021-01-05 14:51:23', '2021-01-05 18:46:31'],
|
||||
[6, 'test6', 16, 600, '2020-08-06 20:11:10', '6579e3af2768cdf52ec84c1f320333f68401dc6e', 'no files no peers', '2021-01-06 14:51:23', '2021-01-06 18:46:31'],
|
||||
]));
|
||||
|
||||
Capsule::table('torrent_peers', null, $connection)->insert(
|
||||
combineValues(['id', 'torrent_id', 'seeds', 'total_seeds', 'leechers', 'total_leechers', 'created_at', 'updated_at'], [
|
||||
[1, 1, 1, 1, 1, 1, '2021-01-01 14:51:23', '2021-01-01 17:46:31'],
|
||||
[2, 2, 2, 2, 2, 2, '2021-01-02 14:51:23', '2021-01-02 17:46:31'],
|
||||
[3, 3, 3, 3, 3, 3, '2021-01-03 14:51:23', '2021-01-03 17:46:31'],
|
||||
[4, 4, NULL, 4, 4, 4, '2021-01-04 14:51:23', '2021-01-04 17:46:31'],
|
||||
]));
|
||||
|
||||
Capsule::table('torrent_previewable_files', null, $connection)->insert(
|
||||
combineValues(['id', 'torrent_id', 'file_index', 'filepath', 'size', 'progress', 'note', 'created_at', 'updated_at'], [
|
||||
[1, 1, 0, 'test1_file1.mkv', 1024, 200, 'no file propreties', '2021-01-01 14:51:23', '2021-01-01 17:46:31'],
|
||||
[2, 2, 0, 'test2_file1.mkv', 2048, 870, NULL, '2021-01-02 14:51:23', '2021-01-02 17:46:31'],
|
||||
[3, 2, 1, 'test2_file2.mkv', 3072, 1000, NULL, '2021-01-02 14:51:23', '2021-01-02 17:46:31'],
|
||||
[4, 3, 0, 'test3_file1.mkv', 5568, 870, NULL, '2021-01-03 14:51:23', '2021-01-03 17:46:31'],
|
||||
[5, 4, 0, 'test4_file1.mkv', 4096, 0, NULL, '2021-01-04 14:51:23', '2021-01-04 17:46:31'],
|
||||
[6, 5, 0, 'test5_file1.mkv', 2048, 999, NULL, '2021-01-05 14:51:23', '2021-01-05 17:46:31'],
|
||||
[7, 5, 1, 'test5_file2.mkv', 2560, 890, 'for tst_BaseModel::remove()/destroy()', '2021-01-02 14:55:23', '2021-01-02 17:47:31'],
|
||||
[8, 5, 2, 'test5_file3.mkv', 2570, 896, 'for tst_BaseModel::destroy()', '2021-01-02 14:56:23', '2021-01-02 17:48:31'],
|
||||
]));
|
||||
|
||||
Capsule::table('torrent_previewable_file_properties', null, $connection)->insert(
|
||||
combineValues(['id', 'previewable_file_id', 'name', 'size'], [
|
||||
[1, 2, 'test2_file1', 2],
|
||||
[2, 3, 'test2_file2', 2],
|
||||
[3, 4, 'test3_file1', 4],
|
||||
[4, 5, 'test4_file1', 5],
|
||||
[5, 6, 'test5_file1', 6],
|
||||
]));
|
||||
|
||||
Capsule::table('file_property_properties', null, $connection)->insert(
|
||||
combineValues(['id', 'file_property_id', 'name', 'value', 'created_at', 'updated_at'], [
|
||||
[1, 1, 'test2_file1_property1', 1, '2021-01-01 14:51:23', '2021-01-01 17:46:31'],
|
||||
[2, 2, 'test2_file2_property1', 2, '2021-01-02 14:51:23', '2021-01-02 17:46:31'],
|
||||
[3, 3, 'test3_file1_property1', 3, '2021-01-03 14:51:23', '2021-01-03 17:46:31'],
|
||||
[4, 3, 'test3_file1_property2', 4, '2021-01-04 14:51:23', '2021-01-04 17:46:31'],
|
||||
[5, 4, 'test4_file1_property1', 5, '2021-01-05 14:51:23', '2021-01-05 17:46:31'],
|
||||
[6, 5, 'test5_file1_property1', 6, '2021-01-06 14:51:23', '2021-01-06 17:46:31'],
|
||||
]));
|
||||
|
||||
Capsule::table('torrent_tags', null, $connection)->insert(
|
||||
combineValues(['id', 'name', 'created_at', 'updated_at'], [
|
||||
[1, 'tag1', '2021-01-11 11:51:28', '2021-01-11 23:47:11'],
|
||||
[2, 'tag2', '2021-01-12 11:51:28', '2021-01-12 23:47:11'],
|
||||
[3, 'tag3', '2021-01-13 11:51:28', '2021-01-13 23:47:11'],
|
||||
[4, 'tag4', '2021-01-14 11:51:28', '2021-01-14 23:47:11'],
|
||||
]));
|
||||
|
||||
Capsule::table('tag_torrent', null, $connection)->insert(
|
||||
combineValues(['torrent_id', 'tag_id', 'active', 'created_at', 'updated_at'], [
|
||||
[2, 1, 1, '2021-02-21 17:31:58', '2021-02-21 18:49:22'],
|
||||
[2, 2, 1, '2021-02-22 17:31:58', '2021-02-22 18:49:22'],
|
||||
[2, 3, 0, '2021-02-23 17:31:58', '2021-02-23 18:49:22'],
|
||||
[2, 4, 1, '2021-02-24 17:31:58', '2021-02-24 18:49:22'],
|
||||
[3, 2, 1, '2021-02-24 17:31:58', '2021-02-24 18:49:22'],
|
||||
[3, 4, 1, '2021-02-24 17:31:58', '2021-02-24 18:49:22'],
|
||||
]));
|
||||
|
||||
Capsule::table('tag_properties', null, $connection)->insert(
|
||||
combineValues(['id', 'tag_id', 'color', 'position', 'created_at', 'updated_at'], [
|
||||
[1, 1, 'white', 0, '2021-02-11 12:41:28', '2021-02-11 22:17:11'],
|
||||
[2, 2, 'blue', 1, '2021-02-12 12:41:28', '2021-02-12 22:17:11'],
|
||||
[3, 3, 'red', 2, '2021-02-13 12:41:28', '2021-02-13 22:17:11'],
|
||||
[4, 4, 'orange', 3, '2021-02-14 12:41:28', '2021-02-14 22:17:11'],
|
||||
]));
|
||||
|
||||
Capsule::table('users', null, $connection)->insert(
|
||||
combineValues(['id', 'name'], [
|
||||
[1, 'andrej'],
|
||||
[2, 'silver'],
|
||||
[3, 'peter'],
|
||||
]));
|
||||
|
||||
Capsule::table('roles', null, $connection)->insert(
|
||||
combineValues(['id', 'name'], [
|
||||
[1, 'role one'],
|
||||
[2, 'role two'],
|
||||
[3, 'role three'],
|
||||
]));
|
||||
|
||||
Capsule::table('role_user', null, $connection)->insert(
|
||||
combineValues(['role_id', 'user_id', 'active'], [
|
||||
[1, 1, 1],
|
||||
[2, 1, 0],
|
||||
[3, 1, 1],
|
||||
[2, 2, 1],
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and seed all tables for all connections.
|
||||
*
|
||||
* @param array $connections Connection names
|
||||
* @return void
|
||||
*/
|
||||
function createAndSeedTables(array $connections)
|
||||
{
|
||||
foreach ($connections as $connection) {
|
||||
dropAllTables($connection);
|
||||
createTables($connection);
|
||||
seedTables($connection);
|
||||
}
|
||||
}
|
||||
|
||||
$capsule = new Capsule;
|
||||
|
||||
$capsule->setAsGlobal();
|
||||
$capsule->bootEloquent();
|
||||
|
||||
$configs = [
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => \getenv('DB_MYSQL_HOST') ?? '127.0.0.1',
|
||||
'port' => \getenv('DB_MYSQL_PORT') ?? '3306',
|
||||
'database' => \getenv('DB_MYSQL_DATABASE') ?? '',
|
||||
'username' => \getenv('DB_MYSQL_USERNAME') ?? 'root',
|
||||
'password' => \getenv('DB_MYSQL_PASSWORD') ?? '',
|
||||
'charset' => \getenv('DB_MYSQL_CHARSET') ?? 'utf8mb4',
|
||||
'collation' => \getenv('DB_MYSQL_COLLATION') ?? 'utf8mb4_0900_ai_ci',
|
||||
'timezone' => '+00:00',
|
||||
'prefix' => '',
|
||||
],
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'database' => \getenv('DB_SQLITE_DATABASE') ?? '',
|
||||
'prefix' => '',
|
||||
'foreign_key_constraints' => true,
|
||||
],
|
||||
];
|
||||
|
||||
addConnections($capsule, $configs);
|
||||
createAndSeedTables(array_keys($configs));
|
||||
@@ -70,12 +70,12 @@ CREATE TABLE `torrents` (
|
||||
--
|
||||
|
||||
INSERT INTO `torrents` (`id`, `name`, `size`, `progress`, `added_on`, `hash`, `note`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'test1', 11, 100, '2020-08-01 20:11:10', '1579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-01 14:51:23', '2021-01-01 17:46:31'),
|
||||
(2, 'test2', 12, 200, '2020-08-02 20:11:10', '2579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-02 14:51:23', '2021-01-02 17:46:31'),
|
||||
(3, 'test3', 13, 300, '2020-08-03 20:11:10', '3579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-03 14:51:23', '2021-01-03 17:46:31'),
|
||||
(4, 'test4', 14, 400, '2020-08-04 20:11:10', '4579e3af2768cdf52ec84c1f320333f68401dc6e', 'after update revert updated_at', '2021-01-04 14:51:23', '2021-01-04 17:46:31'),
|
||||
(5, 'test5', 15, 500, '2020-08-05 20:11:10', '5579e3af2768cdf52ec84c1f320333f68401dc6e', 'no peers', '2021-01-05 14:51:23', '2021-01-05 17:46:31'),
|
||||
(6, 'test6', 16, 600, '2020-08-06 20:11:10', '6579e3af2768cdf52ec84c1f320333f68401dc6e', 'no files no peers', '2021-01-06 14:51:23', '2021-01-06 17:46:31');
|
||||
(1, 'test1', 11, 100, '2020-08-01 20:11:10', '1579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-01 14:51:23', '2021-01-01 18:46:31'),
|
||||
(2, 'test2', 12, 200, '2020-08-02 20:11:10', '2579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-02 14:51:23', '2021-01-02 18:46:31'),
|
||||
(3, 'test3', 13, 300, '2020-08-03 20:11:10', '3579e3af2768cdf52ec84c1f320333f68401dc6e', NULL, '2021-01-03 14:51:23', '2021-01-03 18:46:31'),
|
||||
(4, 'test4', 14, 400, '2020-08-04 20:11:10', '4579e3af2768cdf52ec84c1f320333f68401dc6e', 'after update revert updated_at', '2021-01-04 14:51:23', '2021-01-04 18:46:31'),
|
||||
(5, 'test5', 15, 500, '2020-08-05 20:11:10', '5579e3af2768cdf52ec84c1f320333f68401dc6e', 'no peers', '2021-01-05 14:51:23', '2021-01-05 18:46:31'),
|
||||
(6, 'test6', 16, 600, '2020-08-06 20:11:10', '6579e3af2768cdf52ec84c1f320333f68401dc6e', 'no files no peers', '2021-01-06 14:51:23', '2021-01-06 18:46:31');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
@@ -406,7 +406,7 @@ ALTER TABLE `settings`
|
||||
--
|
||||
ALTER TABLE `torrents`
|
||||
ADD PRIMARY KEY (`id`),
|
||||
ADD KEY `torrents_name_index` (`name`);
|
||||
ADD KEY `torrents_name_unique` (`name`);
|
||||
|
||||
--
|
||||
-- Indexes for table `torrent_peers`
|
||||
@@ -480,7 +480,7 @@ ADD UNIQUE KEY `roles_name_unique` (`name`);
|
||||
--
|
||||
ALTER TABLE `role_user`
|
||||
ADD PRIMARY KEY (`role_id`, `user_id`),
|
||||
ADD KEY `role_user_role_id_foreign` (`role_id`);
|
||||
ADD KEY `role_user_user_id_foreign` (`user_id`);
|
||||
|
||||
--
|
||||
-- AUTO_INCREMENT for dumped tables
|
||||
@@ -579,7 +579,7 @@ ALTER TABLE `tag_torrent`
|
||||
-- Constraints for table `tag_properties`
|
||||
--
|
||||
ALTER TABLE `tag_properties`
|
||||
ADD CONSTRAINT `torrent_tags_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `torrent_tags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
ADD CONSTRAINT `tag_properties_tag_id_foreign` FOREIGN KEY (`tag_id`) REFERENCES `torrent_tags` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
--
|
||||
-- Constraints for table `role_user`
|
||||
|
||||
@@ -73,3 +73,37 @@ include(src/src.pri)
|
||||
release {
|
||||
target.CONFIG += no_default_install
|
||||
}
|
||||
|
||||
# Create the SQLite database
|
||||
# ---
|
||||
|
||||
build_tests {
|
||||
# Default SQLite test database, can be overriden by DB_SQLITE_DATABASE env. variable
|
||||
TINYORM_SQLITE_DATABASE = $$quote($$TINYORM_BUILD_TREE/tests/q_tinyorm_test_1.sqlite3)
|
||||
|
||||
sqlitedatabase.target = sqlitedatabase
|
||||
sqlitedatabase.dbname = $$TINYORM_SQLITE_DATABASE
|
||||
sqlitedatabase.commands = touch $$sqlitedatabase.dbname
|
||||
sqlitedatabase.depends = sqlitedatabase_message
|
||||
|
||||
sqlitedatabase_message.commands = @echo Creating SQLite database at $$sqlitedatabase.dbname
|
||||
|
||||
QMAKE_EXTRA_TARGETS += sqlitedatabase sqlitedatabase_message
|
||||
|
||||
!exists($$TINYORM_SQLITE_DATABASE) {
|
||||
POST_TARGETDEPS += sqlitedatabase
|
||||
}
|
||||
|
||||
# Set path to the SQLite database
|
||||
# ---
|
||||
contains(TEMPLATE, vc.*): DEFINES += TINYORM_SQLITE_DATABASE=\"$$TINYORM_SQLITE_DATABASE\"
|
||||
else: DEFINES += TINYORM_SQLITE_DATABASE=$$shell_quote(\"$$TINYORM_SQLITE_DATABASE\")
|
||||
}
|
||||
|
||||
# Clean the SQLite database
|
||||
# ---
|
||||
|
||||
build_tests {
|
||||
QMAKE_CLEAN = $$TINYORM_SQLITE_DATABASE
|
||||
QMAKE_DISTCLEAN = $$TINYORM_SQLITE_DATABASE
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ CONFIG *= qt console testcase link_prl
|
||||
|
||||
DEFINES += PROJECT_TINYORM_TEST
|
||||
|
||||
# Enable code needed by tests, eg connection overriding in the BaseModel
|
||||
DEFINES += TINYORM_TESTS_CODE
|
||||
|
||||
# Use TinyORM's library precompiled headers (PCH)
|
||||
# ---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user