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:
silverqx
2021-03-26 16:07:41 +01:00
parent 50ee6625a4
commit 68446e3530
70 changed files with 4205 additions and 860 deletions

3
.gitignore vendored
View File

@@ -74,3 +74,6 @@ Thumbs.db
*.dll
*.exe
# Test's Database
**/tests/**/testdata/vendor/
**/tests/**/testdata/dotenv.ps1

View File

@@ -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:
------------------------------

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 \

View File

@@ -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

View File

@@ -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;

View File

@@ -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:

View 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

View File

@@ -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>

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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;

View 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

View 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

View File

@@ -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. */

View 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

View File

@@ -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. */

View File

@@ -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
{

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>();

View File

@@ -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);

View File

@@ -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();
}
}

View 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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View File

@@ -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)

View 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

View File

@@ -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

View File

@@ -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 \

View File

@@ -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
# ---

View File

@@ -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.");

View File

@@ -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"

View File

@@ -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)

View File

@@ -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 \

View File

@@ -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"};
};

View File

@@ -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

View File

@@ -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"};
};

View File

@@ -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

View File

@@ -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. */

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"};
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
};

View File

@@ -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);

View File

@@ -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
{

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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();
};
}

View File

@@ -0,0 +1,5 @@
{
"require": {
"illuminate/database": "^8.33"
}
}

1631
tests/auto/utils/testdata/composer.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

View 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));

View File

@@ -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`

View File

@@ -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
}

View File

@@ -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)
# ---