diff --git a/cmake/Modules/TinySources.cmake b/cmake/Modules/TinySources.cmake index b0aa22fed..8beb68d83 100644 --- a/cmake/Modules/TinySources.cmake +++ b/cmake/Modules/TinySources.cmake @@ -224,7 +224,6 @@ function(tinyorm_sources out_headers out_sources) tiny/exceptions/relationnotfounderror.cpp tiny/exceptions/relationnotloadederror.cpp tiny/tinytypes.cpp - tiny/types/syncchanges.cpp tiny/utils/attribute.cpp ) endif() diff --git a/include/orm/tiny/relations/concerns/interactswithpivottable.hpp b/include/orm/tiny/relations/concerns/interactswithpivottable.hpp index 9267092dd..a487f2d0c 100644 --- a/include/orm/tiny/relations/concerns/interactswithpivottable.hpp +++ b/include/orm/tiny/relations/concerns/interactswithpivottable.hpp @@ -451,19 +451,19 @@ namespace Concerns if (detaching && !detach.isEmpty()) { this->detach(detach); - changes["detached"] = std::move(detach); + changes.at(Detached) = std::move(detach); } /* Now we are finally ready to attach the new records. Note that we'll disable touching until after the entire operation is complete so we don't fire a ton of touch operations until we are totally done syncing the records. */ - changes.merge( + changes.template merge( attachNew(idsWithAttributes, current, false)); /* Once we have finished attaching or detaching the records, we will see if we have done any attaching or detaching, and if we have we will touch these relationships if they are configured to touch on any database updates. */ - if (!changes["attached"].isEmpty() || !changes["updated"].isEmpty()) + if (!changes.at(Attached).isEmpty() || !changes.at(Updated_).isEmpty()) touchIfTouching_(); return changes; @@ -876,7 +876,7 @@ namespace Concerns if (!current.contains(id)) { attach(id, attributes, touch); - changes["attached"] << id; + changes.at(Attached) << id; } /* If the pivot record already exists, we'll try to update the attributes @@ -886,7 +886,7 @@ namespace Concerns else if (!attributes.isEmpty() && updateExistingPivot(id, attributes, touch) ) - changes["updated"] << id; + changes.at(Updated_) << id; } return changes; diff --git a/include/orm/tiny/types/syncchanges.hpp b/include/orm/tiny/types/syncchanges.hpp index 5842173d7..a1933a3ab 100644 --- a/include/orm/tiny/types/syncchanges.hpp +++ b/include/orm/tiny/types/syncchanges.hpp @@ -9,55 +9,181 @@ TINY_SYSTEM_HEADER #include #include -#include #if defined(__clang__) || (defined(_MSC_VER) && _MSC_VER < 1929) # include #endif #include "orm/macros/commonnamespace.hpp" -#include "orm/macros/export.hpp" TINYORM_BEGIN_COMMON_NAMESPACE -namespace Orm::Tiny +namespace Orm +{ + +namespace Constants +{ + inline const QString Attached = QStringLiteral("attached"); + inline const QString Detached = QStringLiteral("detached"); + inline const QString Updated_ = QStringLiteral("updated"); +} // namespace Constants + +namespace Tiny { namespace Types { /*! Result of the sync() related methods in belongs to many relation type. */ - class SHAREDLIB_EXPORT SyncChanges final : // NOLINT(bugprone-exception-escape) - public std::map> + class SyncChanges { public: + /* Container related */ + using key_type = QString; + using mapped_type = QVector; + using value_type = typename std::pair; + using reference = value_type &; + using const_reference = const value_type &; + using ContainerType = typename std::map; + /* Iterators related */ + using iterator = typename ContainerType::iterator; + using const_iterator = typename ContainerType::const_iterator; + using reverse_iterator = typename ContainerType::reverse_iterator; + using const_reverse_iterator = typename ContainerType::const_reverse_iterator; + using difference_type = typename ContainerType::difference_type; + using size_type = typename ContainerType::size_type; + using mapped_size_type = typename mapped_type::size_type; + /*! Constructor. */ - SyncChanges(); + inline SyncChanges(); + /*! Default Destructor. */ + inline ~SyncChanges() = default; + + /*! Copy constructor. */ + inline SyncChanges(const SyncChanges &) = default; + /*! Copy assignment operator. */ + inline SyncChanges &operator=(const SyncChanges &) = default; + /*! Move constructor. */ + inline SyncChanges(SyncChanges &&) // NOLINT(bugprone-exception-escape) + noexcept(std::is_nothrow_move_constructible_v) = default; // NOLINT(performance-noexcept-move-constructor) + /*! Move assignment operator. */ + inline SyncChanges &operator=(SyncChanges &&) // NOLINT(bugprone-exception-escape) + noexcept(std::is_nothrow_move_assignable_v) = default; // NOLINT(performance-noexcept-move-constructor) + /* SyncChanges related */ /*! Merge changes into the current instance. */ template SyncChanges &merge(SyncChanges &&changes); /*! Determine if the given key is supported. */ - static bool isValidKey(const QString &key); + inline bool isValidKey(const QString &key) const; + + /* Getters */ + /*! Vector of attached models IDs. */ + inline QVector &attached(); + /*! Vector of attached models IDs, const version. */ + inline const QVector &attached() const; + /*! Vector of detached models IDs. */ + inline QVector &detached(); + /*! Vector of detached models IDs, const version. */ + inline const QVector &detached() const; + /*! Vector of model IDs for which pivot records were updated in the pivot + table. */ + inline QVector &updated(); + /*! Vector of model IDs for which pivot records were updated in the pivot table, + const version. */ + inline const QVector &updated() const; + + /* std::map proxy methods */ + /*! Returns an iterator to the beginning. */ + inline iterator begin() noexcept; + /*! Returns an iterator to the end. */ + inline iterator end() noexcept; + /*! Returns an iterator to the beginning, const version. */ + inline const_iterator begin() const noexcept; + /*! Returns an iterator to the end, const version. */ + inline const_iterator end() const noexcept; + /*! Returns an iterator to the beginning, const version. */ + inline const_iterator cbegin() const noexcept; + /*! Returns an iterator to the end, const version. */ + inline const_iterator cend() const noexcept; + + /*! Returns a reverse iterator to the beginning. */ + inline reverse_iterator rbegin() noexcept; + /*! Returns a reverse iterator to the end. */ + inline reverse_iterator rend() noexcept; + /*! Returns a reverse iterator to the beginning, const version. */ + inline const_reverse_iterator rbegin() const noexcept; + /*! Returns a reverse iterator to the end, const version. */ + inline const_reverse_iterator rend() const noexcept; + /*! Returns a reverse iterator to the beginning, const version. */ + inline const_reverse_iterator crbegin() const noexcept; + /*! Returns a reverse iterator to the end, const version. */ + inline const_reverse_iterator crend() const noexcept; + + /* Capacity */ + /*! Returns the number of all elements (attached, detached, updated). */ + inline size_type size() const + noexcept(std::is_nothrow_invocable_v); + /*! Checks whether the container is empty (all attached, detached, updated). */ + inline bool empty() const + noexcept(std::is_nothrow_invocable_r_v); + + /* Element access */ + /*! Access specified element with bounds checking. */ + inline mapped_type &at(const key_type &key); + /*! Access specified element with bounds checking. */ + inline const mapped_type &at(const key_type &key) const; + /*! Access specified element with bounds checking, alias to the at(). */ + inline mapped_type &operator[](const key_type &key); + /*! Access specified element with bounds checking, alias to the at(). */ + inline const mapped_type &operator[](const key_type &key) const; + + /* Modifiers */ + /*! Clears the contents. */ + inline void clear(); + + /*! Swaps the contents. */ + inline void swap(SyncChanges &right) + noexcept(std::is_nothrow_swappable_v); + + /* Comparison */ + /*! Comparison operator for the SyncChanges. */ + inline bool operator==(const SyncChanges &) const = default; protected: /*! Cast the given key to primary key type. */ template inline T castKey(const QVariant &key) const; - private: - /*! All of the supported keys. */ - static const std::unordered_set &syncKeys(); + /*! SyncChanges storage. */ + ContainerType m_data; }; /* public */ + SyncChanges::SyncChanges() + : m_data {{Constants::Attached, {}}, + {Constants::Detached, {}}, + {Constants::Updated_, {}}} + {} + + /* SyncChanges related */ + template SyncChanges &SyncChanges::merge(SyncChanges &&changes) { + /* I can't find a better place for this check, an alternative is to add + the dataSize() -> m_data.size() public method and wrap it in + the ifdef TINYORM_TESTS_CODE, but I don't like this approach as it breaks + API compatibility. */ + + Q_ASSERT(m_data.size() == 3); + for (auto &&[key, values] : changes) { - auto ¤tValues = (*this)[key]; + auto ¤tValues = at(key); { // If the current key value is empty, then simply move a new values @@ -104,6 +230,162 @@ namespace Types return *this; } + bool SyncChanges::isValidKey(const QString &key) const + { + return m_data.contains(key); + } + + /* Getters */ + + QVector &SyncChanges::attached() + { + return m_data.at(Constants::Attached); + } + + const QVector &SyncChanges::attached() const + { + return m_data.at(Constants::Attached); + } + + QVector &SyncChanges::detached() + { + return m_data.at(Constants::Detached); + } + + const QVector &SyncChanges::detached() const + { + return m_data.at(Constants::Detached); + } + + QVector &SyncChanges::updated() + { + return m_data.at(Constants::Updated_); + } + + const QVector &SyncChanges::updated() const + { + return m_data.at(Constants::Updated_); + } + + /* std::map proxy methods */ + + SyncChanges::iterator SyncChanges::begin() noexcept + { + return m_data.begin(); + } + + SyncChanges::iterator SyncChanges::end() noexcept + { + return m_data.end(); + } + + SyncChanges::const_iterator SyncChanges::begin() const noexcept + { + return m_data.begin(); + } + + SyncChanges::const_iterator SyncChanges::end() const noexcept + { + return m_data.end(); + } + + SyncChanges::const_iterator SyncChanges::cbegin() const noexcept + { + return m_data.cbegin(); + } + + SyncChanges::const_iterator SyncChanges::cend() const noexcept + { + return m_data.cend(); + } + + SyncChanges::reverse_iterator SyncChanges::rbegin() noexcept + { + return m_data.rbegin(); + } + + SyncChanges::reverse_iterator SyncChanges::rend() noexcept + { + return m_data.rend(); + } + + SyncChanges::const_reverse_iterator SyncChanges::rbegin() const noexcept + { + return m_data.rbegin(); + } + + SyncChanges::const_reverse_iterator SyncChanges::rend() const noexcept + { + return m_data.rend(); + } + + SyncChanges::const_reverse_iterator SyncChanges::crbegin() const noexcept + { + return m_data.crbegin(); + } + + SyncChanges::const_reverse_iterator SyncChanges::crend() const noexcept + { + return m_data.crend(); + } + + /* Capacity */ + + SyncChanges::size_type SyncChanges::size() const + noexcept(std::is_nothrow_invocable_v) + { + return static_cast(attached().size()) + + static_cast(detached().size()) + + static_cast(updated().size()); + } + + bool SyncChanges::empty() const + noexcept(std::is_nothrow_invocable_r_v) + { + return attached().empty() && detached().empty() && updated().empty(); + } + + /* Element access */ + + SyncChanges::mapped_type &SyncChanges::at(const key_type &key) + { + return m_data.at(key); + } + + const SyncChanges::mapped_type &SyncChanges::at(const key_type &key) const + { + return m_data.at(key); + } + + SyncChanges::mapped_type &SyncChanges::operator[](const key_type &key) + { + return m_data.at(key); + } + + const SyncChanges::mapped_type &SyncChanges::operator[](const key_type &key) const + { + return m_data.at(key); + } + + /* Modifiers */ + + void SyncChanges::clear() + { + m_data.at(Constants::Attached).clear(); + m_data.at(Constants::Detached).clear(); + m_data.at(Constants::Updated_).clear(); + } + + void SyncChanges::swap(SyncChanges &right) + noexcept(std::is_nothrow_swappable_v) + { + if (this == std::addressof(right)) + return; + + m_data.swap(right.m_data); + } + /* protected */ template @@ -112,11 +394,20 @@ namespace Types return key.template value(); } + /* Non-member functions */ + + inline void swap(SyncChanges &left, SyncChanges &right) + noexcept(noexcept(left.swap(right))) + { + left.swap(right); + } + } // namespace Types using SyncChanges = Tiny::Types::SyncChanges; -} // namespace Orm::Tiny +} // namespace Tiny +} // namespace Orm TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/tiny/types/syncchanges.cpp b/src/orm/tiny/types/syncchanges.cpp deleted file mode 100644 index 006b50503..000000000 --- a/src/orm/tiny/types/syncchanges.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "orm/tiny/types/syncchanges.hpp" - -TINYORM_BEGIN_COMMON_NAMESPACE - -namespace Orm::Tiny::Types -{ - -/* public */ - -SyncChanges::SyncChanges() - : map {{QStringLiteral("attached"), {}}, - {QStringLiteral("detached"), {}}, - {QStringLiteral("updated"), {}}} -{} - -bool SyncChanges::isValidKey(const QString &key) -{ - // FUTURE simply use contains(key) and remove syncKeys(), but this needs to = delete all methods that can modify this map as it's derived from the std::map because now an user can simply add another xyz key to the map and this method would return incorrect results, even better will be to define std::map as data member and not deriving from it silverqx - return syncKeys().contains(key); -} - -/* private */ - -const std::unordered_set &Types::SyncChanges::syncKeys() -{ - static const std::unordered_set cached { - QStringLiteral("attached"), - QStringLiteral("detached"), - QStringLiteral("updated") - }; - - return cached; -} - -} // namespace Orm::Tiny::Types - -TINYORM_END_COMMON_NAMESPACE diff --git a/src/src.pri b/src/src.pri index bf55f27dc..93fae4c53 100644 --- a/src/src.pri +++ b/src/src.pri @@ -63,7 +63,6 @@ sourcesList += \ $$PWD/orm/tiny/exceptions/relationnotfounderror.cpp \ $$PWD/orm/tiny/exceptions/relationnotloadederror.cpp \ $$PWD/orm/tiny/tinytypes.cpp \ - $$PWD/orm/tiny/types/syncchanges.cpp \ $$PWD/orm/tiny/utils/attribute.cpp \ !disable_orm|!disable_tom: \ diff --git a/tests/auto/functional/orm/tiny/relations_insrt_updt/tst_relations_inserting_updating.cpp b/tests/auto/functional/orm/tiny/relations_insrt_updt/tst_relations_inserting_updating.cpp index 61beabbd5..de55f4fb5 100644 --- a/tests/auto/functional/orm/tiny/relations_insrt_updt/tst_relations_inserting_updating.cpp +++ b/tests/auto/functional/orm/tiny/relations_insrt_updt/tst_relations_inserting_updating.cpp @@ -13,9 +13,12 @@ using Models::Tagged; using Models::Torrent; using Models::TorrentPreviewableFile; +using Orm::Constants::Attached; +using Orm::Constants::Detached; using Orm::Constants::ID; using Orm::Constants::NAME; using Orm::Constants::SIZE; +using Orm::Constants::Updated_; using Orm::Exceptions::QueryError; using Orm::One; @@ -2449,16 +2452,15 @@ void tst_Relations_Inserting_Updating::sync_BasicPivot_WithIds() const *torrent103[ID]}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - const auto &detachedVector = changed.at("detached"); + const auto &detachedVector = changed.at(Detached); QCOMPARE(detachedVector.size(), 1); - QVERIFY(changed.at("updated").isEmpty()); + QVERIFY(changed.at(Updated_).isEmpty()); const QVector expectedAttached {torrent100[ID], torrent103[ID]}; const QVector expectedDetached {torrent102[ID]}; @@ -2570,16 +2572,15 @@ void tst_Relations_Inserting_Updating::sync_BasicPivot_IdsWithAttributes() const {torrent103[ID]->value(), {{"active", true}}}}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - const auto &detachedVector = changed.at("detached"); + const auto &detachedVector = changed.at(Detached); QCOMPARE(detachedVector.size(), 1); - const auto &updatedVector = changed.at("updated"); + const auto &updatedVector = changed.at(Updated_); QCOMPARE(updatedVector.size(), 1); const QVector expectedAttached {torrent100[ID], torrent103[ID]}; @@ -2681,16 +2682,15 @@ void tst_Relations_Inserting_Updating::sync_CustomPivot_WithIds() const torrent5->tags()->sync({*tag100[ID], *tag101[ID], *tag103[ID]}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - const auto &detachedVector = changed.at("detached"); + const auto &detachedVector = changed.at(Detached); QCOMPARE(detachedVector.size(), 1); - QVERIFY(changed.at("updated").isEmpty()); + QVERIFY(changed.at(Updated_).isEmpty()); const QVector expectedAttached {tag100[ID], tag103[ID]}; const QVector expectedDetached {tag102[ID]}; @@ -2790,16 +2790,15 @@ void tst_Relations_Inserting_Updating::sync_CustomPivot_IdsWithAttributes() cons {tag103[ID]->value(), {{"active", true}}}}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - const auto &detachedVector = changed.at("detached"); + const auto &detachedVector = changed.at(Detached); QCOMPARE(detachedVector.size(), 1); - const auto &updatedVector = changed.at("updated"); + const auto &updatedVector = changed.at(Updated_); QCOMPARE(updatedVector.size(), 1); const QVector expectedAttached {tag100[ID], tag103[ID]}; @@ -2914,15 +2913,14 @@ void tst_Relations_Inserting_Updating::syncWithoutDetaching_BasicPivot_WithIds() *torrent103[ID]}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - QVERIFY(changed.at("detached").isEmpty()); - QVERIFY(changed.at("updated").isEmpty()); + QVERIFY(changed.at(Detached).isEmpty()); + QVERIFY(changed.at(Updated_).isEmpty()); const QVector expectedAttached {torrent100[ID], torrent103[ID]}; @@ -3033,15 +3031,14 @@ void tst_Relations_Inserting_Updating {torrent103[ID]->value(), {{"active", true}}}}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - QVERIFY(changed.at("detached").isEmpty()); - const auto &updatedVector = changed.at("updated"); + QVERIFY(changed.at(Detached).isEmpty()); + const auto &updatedVector = changed.at(Updated_); QCOMPARE(updatedVector.size(), 1); const QVector expectedAttached {torrent100[ID], torrent103[ID]}; @@ -3142,19 +3139,18 @@ void tst_Relations_Inserting_Updating::syncWithoutDetaching_CustomPivot_WithIds( *tag103[ID]}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - QVERIFY(changed.at("detached").isEmpty()); - QVERIFY(changed.at("updated").isEmpty()); + QVERIFY(changed.at(Detached).isEmpty()); + QVERIFY(changed.at(Updated_).isEmpty()); const QVector expectedAttached {tag100[ID], tag103[ID]}; - for (const auto &attached : changed.at("attached")) + for (const auto &attached : changed.at(Attached)) QVERIFY(expectedAttached.contains(attached)); // Verify tagged values in the database @@ -3249,15 +3245,14 @@ void tst_Relations_Inserting_Updating {tag103[ID]->value(), {{"active", true}}}}); // Verify result - QCOMPARE(changed.size(), static_cast(3)); - QVERIFY(changed.contains("attached")); - QVERIFY(changed.contains("detached")); - QVERIFY(changed.contains("updated")); + QVERIFY(changed.isValidKey(Attached)); + QVERIFY(changed.isValidKey(Detached)); + QVERIFY(changed.isValidKey(Updated_)); - const auto &attachedVector = changed.at("attached"); + const auto &attachedVector = changed.at(Attached); QCOMPARE(attachedVector.size(), 2); - QVERIFY(changed.at("detached").isEmpty()); - const auto &updatedVector = changed.at("updated"); + QVERIFY(changed.at(Detached).isEmpty()); + const auto &updatedVector = changed.at(Updated_); QCOMPARE(updatedVector.size(), 1); const QVector expectedAttached {tag100[ID], tag103[ID]};