diff --git a/include/orm/query/grammars/grammar.hpp b/include/orm/query/grammars/grammar.hpp index ede63e285..e44b7f481 100644 --- a/include/orm/query/grammars/grammar.hpp +++ b/include/orm/query/grammars/grammar.hpp @@ -96,17 +96,19 @@ namespace Orm::Query::Grammars struct SelectComponentValue { /*! The component's compile method. */ - std::function compileMethod; + std::function compileMethod; /*! Determine whether the component is set and is not empty. */ std::function isset; }; + /*! Alias type for the whereXx() methods. */ + using WhereMemFn = std::function; /*! Map the ComponentType to a Grammar::compileXx() methods. */ virtual const QMap & getCompileMap() const = 0; /*! Map the WhereType to a Grammar::whereXx() methods. */ - virtual const std::function & - getWhereMethod(WhereType whereType) const = 0; + virtual const WhereMemFn &getWhereMethod(WhereType whereType) const = 0; /*! Determine whether the 'aggregate' component should be compiled. */ static bool shouldCompileAggregate(const std::optional &aggregate); diff --git a/include/orm/query/grammars/mysqlgrammar.hpp b/include/orm/query/grammars/mysqlgrammar.hpp index b7f05fe3d..68c90d359 100644 --- a/include/orm/query/grammars/mysqlgrammar.hpp +++ b/include/orm/query/grammars/mysqlgrammar.hpp @@ -53,8 +53,7 @@ namespace Orm::Query::Grammars const QMap & getCompileMap() const override; /*! Map the WhereType to a Grammar::whereXx() methods. */ - const std::function & - getWhereMethod(WhereType whereType) const override; + const WhereMemFn &getWhereMethod(WhereType whereType) const override; /*! Compile an update statement without joins into SQL. */ QString diff --git a/include/orm/query/grammars/postgresgrammar.hpp b/include/orm/query/grammars/postgresgrammar.hpp index 6226e1e29..2577eee87 100644 --- a/include/orm/query/grammars/postgresgrammar.hpp +++ b/include/orm/query/grammars/postgresgrammar.hpp @@ -62,8 +62,7 @@ namespace Orm::Query::Grammars const QMap & getCompileMap() const override; /*! Map the WhereType to a Grammar::whereXx() methods. */ - const std::function & - getWhereMethod(WhereType whereType) const override; + const WhereMemFn &getWhereMethod(WhereType whereType) const override; /*! Compile the "select *" portion of the query. */ QString compileColumns(const QueryBuilder &query) const override; diff --git a/include/orm/query/grammars/sqlitegrammar.hpp b/include/orm/query/grammars/sqlitegrammar.hpp index cdd1262a1..86d271970 100644 --- a/include/orm/query/grammars/sqlitegrammar.hpp +++ b/include/orm/query/grammars/sqlitegrammar.hpp @@ -55,8 +55,7 @@ namespace Orm::Query::Grammars const QMap & getCompileMap() const override; /*! Map the WhereType to a Grammar::whereXx() methods. */ - const std::function & - getWhereMethod(WhereType whereType) const override; + const WhereMemFn &getWhereMethod(WhereType whereType) const override; /*! Compile a "where date" clause. */ QString whereDate(const WhereConditionItem &where) const; diff --git a/src/orm/query/grammars/grammar.cpp b/src/orm/query/grammars/grammar.cpp index 4790b36eb..88c81db3d 100644 --- a/src/orm/query/grammars/grammar.cpp +++ b/src/orm/query/grammars/grammar.cpp @@ -194,7 +194,7 @@ QStringList Grammar::compileComponents(const QueryBuilder &query) const for (const auto &component : compileMap) if (component.isset && component.isset(query)) - sql << std::invoke(component.compileMethod, query); + sql << std::invoke(component.compileMethod, *this, query); return sql; } @@ -268,7 +268,7 @@ QStringList Grammar::compileWheresToVector(const QueryBuilder &query) const for (const auto &where : wheres) compiledWheres << SPACE_IN.arg(where.condition, - std::invoke(getWhereMethod(where.type), where)); + std::invoke(getWhereMethod(where.type), *this, where)); return compiledWheres; } diff --git a/src/orm/query/grammars/mysqlgrammar.cpp b/src/orm/query/grammars/mysqlgrammar.cpp index 25bd969ad..7383c0c5c 100644 --- a/src/orm/query/grammars/mysqlgrammar.cpp +++ b/src/orm/query/grammars/mysqlgrammar.cpp @@ -1,6 +1,5 @@ #include "orm/query/grammars/mysqlgrammar.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/mysqlconnection.hpp" #include "orm/query/querybuilder.hpp" @@ -105,18 +104,20 @@ MySqlGrammar::getCompileMap() const 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const QueryBuilder &query) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the MySqlGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), query); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 - T_THREAD_LOCAL static const QMap cached { {SelectComponentType::AGGREGATE, {bind(&MySqlGrammar::compileAggregate), [](const auto &query) @@ -148,28 +149,30 @@ MySqlGrammar::getCompileMap() const return cached; } -const std::function & +const Grammar::WhereMemFn & MySqlGrammar::getWhereMethod(const WhereType whereType) const { /* Needed, because some compileXx() methods are overloaded, this way I will capture 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const WhereConditionItem &where) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the MySqlGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), where); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 // An order has to be the same as in enum struct WhereType // FUTURE QHash would has faster lookup, I should choose QHash, fix also another Grammars silverx - T_THREAD_LOCAL - static const QVector> cached { + static const QVector cached { bind(&MySqlGrammar::whereBasic), bind(&MySqlGrammar::whereNested), bind(&MySqlGrammar::whereColumn), @@ -190,7 +193,6 @@ MySqlGrammar::getWhereMethod(const WhereType whereType) const bind(&MySqlGrammar::whereYear), }; - T_THREAD_LOCAL static const auto size = cached.size(); // Check if whereType is in the range, just for sure 😏 diff --git a/src/orm/query/grammars/postgresgrammar.cpp b/src/orm/query/grammars/postgresgrammar.cpp index 46df1e785..2c0238b79 100644 --- a/src/orm/query/grammars/postgresgrammar.cpp +++ b/src/orm/query/grammars/postgresgrammar.cpp @@ -1,6 +1,5 @@ #include "orm/query/grammars/postgresgrammar.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/query/querybuilder.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -113,18 +112,20 @@ PostgresGrammar::getCompileMap() const 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const QueryBuilder &query) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the PostgresGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), query); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 - T_THREAD_LOCAL static const QMap cached { {SelectComponentType::AGGREGATE, {bind(&PostgresGrammar::compileAggregate), [](const auto &query) @@ -155,28 +156,30 @@ PostgresGrammar::getCompileMap() const return cached; } -const std::function & +const Grammar::WhereMemFn & PostgresGrammar::getWhereMethod(const WhereType whereType) const { /* Needed, because some compileXx() methods are overloaded, this way I will capture 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const WhereConditionItem &query) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the PostgresGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), query); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 // An order has to be the same as in enum struct WhereType // FUTURE QHash would has faster lookup, I should choose QHash, fix also another Grammars silverx - T_THREAD_LOCAL - static const QVector> cached { + static const QVector cached { bind(&PostgresGrammar::whereBasic), bind(&PostgresGrammar::whereNested), bind(&PostgresGrammar::whereColumn), @@ -197,7 +200,6 @@ PostgresGrammar::getWhereMethod(const WhereType whereType) const bind(&PostgresGrammar::whereYear), }; - T_THREAD_LOCAL static const auto size = cached.size(); // Check if whereType is in the range, just for sure 😏 diff --git a/src/orm/query/grammars/sqlitegrammar.cpp b/src/orm/query/grammars/sqlitegrammar.cpp index 034b66feb..33c51f29f 100644 --- a/src/orm/query/grammars/sqlitegrammar.cpp +++ b/src/orm/query/grammars/sqlitegrammar.cpp @@ -1,6 +1,5 @@ #include "orm/query/grammars/sqlitegrammar.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/query/querybuilder.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -92,18 +91,20 @@ SQLiteGrammar::getCompileMap() const 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const QueryBuilder &query) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the SQLiteGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), query); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 - T_THREAD_LOCAL static const QMap cached { {SelectComponentType::AGGREGATE, {bind(&SQLiteGrammar::compileAggregate), [](const auto &query) @@ -134,27 +135,29 @@ SQLiteGrammar::getCompileMap() const return cached; } -const std::function & +const Grammar::WhereMemFn & SQLiteGrammar::getWhereMethod(const WhereType whereType) const { /* Needed, because some compileXx() methods are overloaded, this way I will capture 'this' reference and the compileMethod rvalue reference in the following lambda and simply save std::function<> in the SelectComponentValue's compileMethod data member. */ - const auto bind = [this](auto &&compileMethod) + const auto bind = [](auto &&compileMethod) { - return [this, - compileMethod = std::forward(compileMethod)] - (const auto &query) + return [compileMethod = std::forward(compileMethod)] + (const Grammar &grammar, const WhereConditionItem &query) { - return std::invoke(compileMethod, this, query); + /* We can be at 100% sure that this is the SQLiteGrammar instance because + this method is virtual; used the reinterpret_cast<> to avoid useless + and slower dynamic_cast<>. */ + return std::invoke(compileMethod, + reinterpret_cast(grammar), query); }; }; // Pointers to a where member methods by whereType, yes yes c++ 😂 // An order has to be the same as in enum struct WhereType - T_THREAD_LOCAL - static const QVector> cached { + static const QVector cached { bind(&SQLiteGrammar::whereBasic), bind(&SQLiteGrammar::whereNested), bind(&SQLiteGrammar::whereColumn), @@ -175,7 +178,6 @@ SQLiteGrammar::getWhereMethod(const WhereType whereType) const bind(&SQLiteGrammar::whereYear), }; - T_THREAD_LOCAL static const auto size = cached.size(); // Check if whereType is in the range, just for sure 😏 diff --git a/src/orm/schema/grammars/mysqlschemagrammar.cpp b/src/orm/schema/grammars/mysqlschemagrammar.cpp index e877fa75c..09c6a9fab 100644 --- a/src/orm/schema/grammars/mysqlschemagrammar.cpp +++ b/src/orm/schema/grammars/mysqlschemagrammar.cpp @@ -3,7 +3,6 @@ #include #include "orm/databaseconnection.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/utils/type.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -281,7 +280,6 @@ MySqlSchemaGrammar::invokeCompileMethod(const CommandDefinition &command, I have to map by QString instead of enum struct because a command.name is used to look up, I could use enum struct but I would have to map QString(command.name) -> enum. */ - T_THREAD_LOCAL static const std::unordered_map cached { {Add, bind(&MySqlSchemaGrammar::compileAdd)}, {Rename, bind(&MySqlSchemaGrammar::compileRename)}, diff --git a/src/orm/schema/grammars/postgresschemagrammar.cpp b/src/orm/schema/grammars/postgresschemagrammar.cpp index 2fafa1a2b..7d9f07a05 100644 --- a/src/orm/schema/grammars/postgresschemagrammar.cpp +++ b/src/orm/schema/grammars/postgresschemagrammar.cpp @@ -3,7 +3,6 @@ #include #include "orm/databaseconnection.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/utils/type.hpp" TINYORM_BEGIN_COMMON_NAMESPACE @@ -338,7 +337,6 @@ PostgresSchemaGrammar::invokeCompileMethod(const CommandDefinition &command, I have to map by QString instead of enum struct because a command.name is used to look up, I could use enum struct but I would have to map QString(command.name) -> enum. */ - T_THREAD_LOCAL static const std::unordered_map cached { {Add, bind(&PostgresSchemaGrammar::compileAdd)}, {Rename, bind(&PostgresSchemaGrammar::compileRename)}, diff --git a/src/orm/schema/grammars/sqliteschemagrammar.cpp b/src/orm/schema/grammars/sqliteschemagrammar.cpp index 7d03bc730..001331f6f 100644 --- a/src/orm/schema/grammars/sqliteschemagrammar.cpp +++ b/src/orm/schema/grammars/sqliteschemagrammar.cpp @@ -9,7 +9,6 @@ #include #include "orm/exceptions/runtimeerror.hpp" -#include "orm/macros/threadlocal.hpp" #include "orm/schema/blueprint.hpp" #include "orm/utils/type.hpp" @@ -249,7 +248,6 @@ SQLiteSchemaGrammar::invokeCompileMethod(const CommandDefinition &command, I have to map by QString instead of enum struct because a command.name is used to look up, I could use enum struct but I would have to map QString(command.name) -> enum. */ - T_THREAD_LOCAL static const std::unordered_map cached { {Add, bind(&SQLiteSchemaGrammar::compileAdd)}, {Rename, bind(&SQLiteSchemaGrammar::compileRename)}, diff --git a/tests/auto/functional/orm/databasemanager/tst_databasemanager.cpp b/tests/auto/functional/orm/databasemanager/tst_databasemanager.cpp index fcaa3d247..4b0044921 100644 --- a/tests/auto/functional/orm/databasemanager/tst_databasemanager.cpp +++ b/tests/auto/functional/orm/databasemanager/tst_databasemanager.cpp @@ -3,7 +3,9 @@ #include "orm/constants.hpp" #include "orm/databasemanager.hpp" +#include "orm/db.hpp" #include "orm/exceptions/sqlitedatabasedoesnotexisterror.hpp" +#include "orm/query/querybuilder.hpp" #include "orm/utils/type.hpp" #include "databases.hpp" @@ -45,6 +47,7 @@ using Orm::Constants::sslrootcert; using Orm::Constants::username_; using Orm::Constants::verify_full; +using Orm::DB; using Orm::DatabaseManager; using Orm::Exceptions::SQLiteDatabaseDoesNotExistError; using Orm::QtTimeZoneConfig; @@ -77,6 +80,8 @@ private Q_SLOTS: void sqlite_CheckDatabaseExists_True() const; void sqlite_CheckDatabaseExists_False() const; + void addUseAndRemoveConnection_FiveTimes() const; + // NOLINTNEXTLINE(readability-redundant-access-specifiers) private: /*! Test case class name. */ @@ -88,6 +93,8 @@ private Q_SLOTS: /*! The Database Manager used in this test case. */ std::shared_ptr m_dm {}; + /*! Number of connections created in the initTestCase() method. */ + QStringList::size_type m_initialConnectionsCount = 0; }; /* private slots */ @@ -95,9 +102,17 @@ private Q_SLOTS: // NOLINTBEGIN(readability-convert-member-functions-to-static) void tst_DatabaseManager::initTestCase() { - // Default connection must be empty - m_dm = DatabaseManager::create(EMPTY); + const auto connections = Databases::createConnections({Databases::MYSQL}); + + if (connections.isEmpty()) + QSKIP(TestUtils::AutoTestSkippedAny.arg(TypeUtils::classPureBasename(*this)) + .toUtf8().constData(), ); + + m_dm = Databases::manager(); + // Used to test and compare number of connections in DM (or opened connections) + m_initialConnectionsCount = connections.size(); + // Default connection must be empty QVERIFY(m_dm->getDefaultConnection().isEmpty()); } @@ -137,7 +152,7 @@ void tst_DatabaseManager::removeConnection_Connected() const QCOMPARE(connection.driverName(), driverName); QCOMPARE(openedConnections.size(), 1); QCOMPARE(openedConnections.first(), *connectionName); - QCOMPARE(m_dm->connectionNames().size(), 1); + QCOMPARE(m_dm->connectionNames().size(), m_initialConnectionsCount + 1); QVERIFY(m_dm->getDefaultConnection().isEmpty()); // Remove opened connection @@ -145,7 +160,7 @@ void tst_DatabaseManager::removeConnection_Connected() const QVERIFY(m_dm->getDefaultConnection().isEmpty()); QVERIFY(m_dm->openedConnectionNames().isEmpty()); - QVERIFY(m_dm->connectionNames().isEmpty()); + QCOMPARE(m_dm->connectionNames().size(), m_initialConnectionsCount); // Restore defaults m_dm->resetDefaultConnection(); @@ -171,14 +186,14 @@ void tst_DatabaseManager::removeConnection_NotConnected() const m_dm->setDefaultConnection(*connectionName); QVERIFY(m_dm->openedConnectionNames().isEmpty()); - QCOMPARE(m_dm->connectionNames().size(), 1); + QCOMPARE(m_dm->connectionNames().size(), m_initialConnectionsCount + 1); QCOMPARE(m_dm->getDefaultConnection(), *connectionName); // Remove database connection that is not opened QVERIFY(Databases::removeConnection(*connectionName)); QVERIFY(m_dm->openedConnectionNames().isEmpty()); - QVERIFY(m_dm->connectionNames().isEmpty()); + QCOMPARE(m_dm->connectionNames().size(), m_initialConnectionsCount); /* When the connection was also a default connection, then DM will reset the default connection. */ QCOMPARE(m_dm->getDefaultConnection(), DatabaseConfiguration::defaultConnectionName); @@ -618,6 +633,27 @@ void tst_DatabaseManager::sqlite_CheckDatabaseExists_False() const QVERIFY(QFile::remove(checkDatabaseExistsFile())); QVERIFY(!QFile::exists(checkDatabaseExistsFile())); } + +void tst_DatabaseManager::addUseAndRemoveConnection_FiveTimes() const +{ + for (auto i = 0; i < 5; ++i) { + // Add a new MYSQL database connection + const auto connectionName = + Databases::createConnectionTempFrom( + Databases::MYSQL, {ClassName, QString::fromUtf8(__func__)}); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) + + if (i == 0 && !connectionName) + QSKIP(TestUtils::AutoTestSkipped + .arg(TypeUtils::classPureBasename(*this), Databases::MYSQL) + .toUtf8().constData(), ); + + // Execute some database query + QCOMPARE(DB::table("users", *connectionName)->count(), 5); + + // Restore + QVERIFY(Databases::removeConnection(*connectionName)); + } +} // NOLINTEND(readability-convert-member-functions-to-static) /* private */