added sortBy() sort by multiple columns overload

- added unit tests
 - updated docs
This commit is contained in:
silverqx
2023-05-31 17:26:40 +02:00
parent 1b990ab361
commit 48c0f6fc02
6 changed files with 580 additions and 0 deletions

View File

@@ -599,6 +599,37 @@ The `sort` method sorts the models collection by primary keys:
You may pass a predicate and projection callbacks to the `sort` method with your own algorithms. Refer to the CPP reference documentation on [`ranges::sort`](https://en.cppreference.com/w/cpp/algorithm/ranges/sort), which is what the `sort` method calls internally.
You can eg. sort by multiple columns, for an alternative method of multi-column sorting look at [sortBy](#method-sortby):
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};
auto sorted = users.sort([](const User *const left,
const User *const right)
{
const auto leftValue = left->getAttribute<QString>("name");
const auto rightValue = right->getAttribute<QString>("name");
if (leftValue == rightValue)
return left->getAttribute<quint64>("votes") <
right->getAttribute<quint64>("votes");
return leftValue < rightValue;
});
/*
{
{{"name", "John"}, {"votes", 150}},
{{"name", "John"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 200}},
{{"name", "Kate"}, {"votes", 350}},
}
*/
The order of equal elements is not guaranteed to be preserved.
:::info
@@ -640,6 +671,43 @@ You may pass the projection callback to determine how to sort the collection's m
}
*/
If you would like to sort your collection by multiple columns, you may pass a vector of comparison lambda expressions that define each sort operation to the `sortBy` method, in the following example is the `name` column sorted in ascending order and the second `votes` column is sorted in descending order:
using AttributeUtils = Orm::Tiny::Utils::Attribute;
ModelsCollection<User> users {
{{"name", "Kate"}, {"votes", 350}},
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 200}},
};
auto sorted = users.sortBy({
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortBy(
left->getAttribute<QString>("name"),
right->getAttribute<QString>("name"));
},
[](const User *const left, const User *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>("votes"),
right->getAttribute<quint64>("votes"));
},
});
/*
{
{{"name", "John"}, {"votes", 200}},
{{"name", "John"}, {"votes", 150}},
{{"name", "Kate"}, {"votes", 350}},
{{"name", "Kate"}, {"votes", 200}},
}
*/
The `AttributeUtils::compareForSortBy` and `compareForSortByDesc` methods are helper methods, they are needed because the Qt framework doesn't define `<=>` spaceship operator on its types, it doesn't support the three-way comparison.
The order of equal elements is not guaranteed to be preserved.
#### `sortByDesc()` {#method-sortbydesc}

View File

@@ -321,6 +321,12 @@ namespace Types
ModelsCollection<ModelRawType *>
sortByDesc(const QString &column);
/*! Sort the collection by the given callback (supports multi-columns sorting). */
ModelsCollection<ModelRawType *>
sortBy(const QVector<std::function<
std::strong_ordering(const ModelRawType *,
const ModelRawType *)>> &callbacks);
/*! Sort the collection using the given projection. */
template<typename P>
ModelsCollection<ModelRawType *>
@@ -349,6 +355,12 @@ namespace Types
ModelsCollection<ModelRawType *>
stableSortByDesc(const QString &column);
/*! Sort the collection by the given callback (supports multi-columns sorting). */
ModelsCollection<ModelRawType *>
stableSortBy(const QVector<std::function<
std::strong_ordering(const ModelRawType *,
const ModelRawType *)>> &callbacks);
/*! Stable sort the collection using the given projection. */
template<typename P>
ModelsCollection<ModelRawType *>
@@ -1371,6 +1383,38 @@ namespace Types
return sortBy<T>(column, true);
}
template<DerivedCollectionModel Model>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType *>
ModelsCollection<Model>::sortBy(
const QVector<std::function<
std::strong_ordering(const ModelRawType *,
const ModelRawType *)>> &callbacks)
{
// Nothing to do
if (this->isEmpty())
return {};
auto result = toPointersCollection();
std::ranges::sort(result, [&callbacks](const ModelRawType *const left,
const ModelRawType *const right)
{
for (const auto &callback : callbacks) {
const auto compared = std::invoke(callback, left, right);
// If the values are the same then sort by the next callback
if (compared == std::strong_ordering::equal)
continue;
return compared == std::strong_ordering::less;
}
return false;
});
return result;
}
template<DerivedCollectionModel Model>
template<typename P>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType *>
@@ -1455,6 +1499,38 @@ namespace Types
return stableSortBy<T>(column, true);
}
template<DerivedCollectionModel Model>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType *>
ModelsCollection<Model>::stableSortBy(
const QVector<std::function<
std::strong_ordering(const ModelRawType *,
const ModelRawType *)>> &callbacks)
{
// Nothing to do
if (this->isEmpty())
return {};
auto result = toPointersCollection();
std::ranges::stable_sort(result, [&callbacks](const ModelRawType *const left,
const ModelRawType *const right)
{
for (const auto &callback : callbacks) {
const auto compared = std::invoke(callback, left, right);
// If the values are the same then sort by the next callback
if (compared == std::strong_ordering::equal)
continue;
return compared == std::strong_ordering::less;
}
return false;
});
return result;
}
template<DerivedCollectionModel Model>
template<typename P>
ModelsCollection<typename ModelsCollection<Model>::ModelRawType *>

View File

@@ -61,6 +61,22 @@ namespace Orm::Tiny::Utils
static QVector<AttributeItem>
exceptAttributesForReplicate(const Model &model,
const std::unordered_set<QString> &except = {});
/*! Compare attributes helper function for the ModelsCollection::sortBy(). */
template<typename T, typename U>
requires ranges::totally_ordered_with<T, U>
static std::strong_ordering compareForSortBy(T &&left, U &&right)
noexcept(noexcept(std::forward<T>(left) == std::forward<U>(right)) &&
noexcept(std::forward<T>(left) < std::forward<U>(right)) &&
noexcept(std::forward<T>(left) > std::forward<U>(right)));
/*! Compare attributes in the descending oder helper function
for the ModelsCollection::sortBy(). */
template<typename T, typename U>
requires ranges::totally_ordered_with<T, U>
static std::strong_ordering compareForSortByDesc(T &&left, U &&right)
noexcept(noexcept(std::forward<T>(left) == std::forward<U>(right)) &&
noexcept(std::forward<T>(left) < std::forward<U>(right)) &&
noexcept(std::forward<T>(left) > std::forward<U>(right)));
};
/* public */
@@ -99,6 +115,40 @@ namespace Orm::Tiny::Utils
| ranges::to<QVector<AttributeItem>>();
}
template<typename T, typename U>
requires ranges::totally_ordered_with<T, U>
std::strong_ordering Attribute::compareForSortBy(T &&left, U &&right)
noexcept(noexcept(std::forward<T>(left) == std::forward<U>(right)) &&
noexcept(std::forward<T>(left) < std::forward<U>(right)) &&
noexcept(std::forward<T>(left) > std::forward<U>(right)))
{
if (std::forward<T>(left) == std::forward<U>(right))
return std::strong_ordering::equal;
if (std::forward<T>(left) < std::forward<U>(right))
return std::strong_ordering::less;
if (std::forward<T>(left) > std::forward<U>(right))
return std::strong_ordering::greater;
Q_UNREACHABLE();
}
template<typename T, typename U>
requires ranges::totally_ordered_with<T, U>
std::strong_ordering Attribute::compareForSortByDesc(T &&left, U &&right)
noexcept(noexcept(std::forward<T>(left) == std::forward<U>(right)) &&
noexcept(std::forward<T>(left) < std::forward<U>(right)) &&
noexcept(std::forward<T>(left) > std::forward<U>(right)))
{
if (std::forward<T>(left) == std::forward<U>(right))
return std::strong_ordering::equal;
if (std::forward<T>(left) < std::forward<U>(right))
return std::strong_ordering::greater;
if (std::forward<T>(left) > std::forward<U>(right))
return std::strong_ordering::less;
Q_UNREACHABLE();
}
} // namespace Orm::Tiny::Utils
TINYORM_END_COMMON_NAMESPACE

View File

@@ -26,6 +26,8 @@ using Orm::Utils::NullVariant;
using TypeUtils = Orm::Utils::Type;
using AttributeUtils = Orm::Tiny::Utils::Attribute;
using TestUtils::Databases;
using Common = TestUtils::Common::Collection;
@@ -128,6 +130,9 @@ private Q_SLOTS:
void sortBy() const;
void sortByDesc() const;
void sortBy_MoreColumns() const;
void sortBy_MoreColumns_SecondDescending() const;
void sortBy_Projection() const;
void sortByDesc_Projection() const;
@@ -143,6 +148,9 @@ private Q_SLOTS:
void stableSortBy() const;
void stableSortByDesc() const;
void stableSortBy_MoreColumns() const;
void stableSortBy_MoreColumns_SecondDescending() const;
void stableSortBy_Projection() const;
void stableSortByDesc_Projection() const;
@@ -1548,6 +1556,93 @@ void tst_Collection_Models::sortByDesc() const
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::sortBy_MoreColumns() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
};
auto sorted = albums.sortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 2}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::sortBy_MoreColumns_SecondDescending() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
};
auto sorted = albums.sortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 1}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::sortBy_Projection() const
{
ModelsCollection<Album> albums {
@@ -1808,6 +1903,101 @@ void tst_Collection_Models::stableSortByDesc() const
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::stableSortBy_MoreColumns() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
};
auto sorted = albums.stableSortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::stableSortBy_MoreColumns_SecondDescending() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
};
auto sorted = albums.stableSortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Models::stableSortBy_Projection() const
{
ModelsCollection<Album> albums {

View File

@@ -26,6 +26,8 @@ using Orm::Utils::NullVariant;
using TypeUtils = Orm::Utils::Type;
using AttributeUtils = Orm::Tiny::Utils::Attribute;
using TestUtils::Databases;
using Common = TestUtils::Common::Collection;
@@ -129,6 +131,9 @@ private Q_SLOTS:
void sortBy() const;
void sortByDesc() const;
void sortBy_MoreColumns() const;
void sortBy_MoreColumns_SecondDescending() const;
void sortBy_Projection() const;
void sortByDesc_Projection() const;
@@ -144,6 +149,9 @@ private Q_SLOTS:
void stableSortBy() const;
void stableSortByDesc() const;
void stableSortBy_MoreColumns() const;
void stableSortBy_MoreColumns_SecondDescending() const;
void stableSortBy_Projection() const;
void stableSortByDesc_Projection() const;
@@ -1926,6 +1934,95 @@ void tst_Collection_Relations::sortByDesc() const
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::sortBy_MoreColumns() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
};
auto albumsInit = albums.toPointers();
auto sorted = albumsInit.sortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 2}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::sortBy_MoreColumns_SecondDescending() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album4"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
};
auto albumsInit = albums.toPointers();
auto sorted = albumsInit.sortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 2}},
{{NAME, "album1"}, {SIZE_, 1}},
{{NAME, "album2"}, {SIZE_, 4}},
{{NAME, "album2"}, {SIZE_, 3}},
{{NAME, "album2"}, {SIZE_, 2}},
{{NAME, "album2"}, {SIZE_, 1}},
{{NAME, "album3"}, {SIZE_, 1}},
{{NAME, "album4"}, {SIZE_, 2}},
{{NAME, "album4"}, {SIZE_, 1}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::sortBy_Projection() const
{
ModelsCollection<Album> albums {
@@ -2196,6 +2293,103 @@ void tst_Collection_Relations::stableSortByDesc() const
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::stableSortBy_MoreColumns() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
};
auto albumsInit = albums.toPointers();
auto sorted = albumsInit.stableSortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::stableSortBy_MoreColumns_SecondDescending() const
{
ModelsCollection<Album> albums {
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
};
auto albumsInit = albums.toPointers();
auto sorted = albumsInit.stableSortBy(
{
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortBy(left->getAttribute<QString>(NAME),
right->getAttribute<QString>(NAME));
},
[](const Album *const left, const Album *const right)
{
return AttributeUtils::compareForSortByDesc(
left->getAttribute<quint64>(SIZE_),
right->getAttribute<quint64>(SIZE_));
},
});
QCOMPARE(typeid (sorted), typeid (ModelsCollection<Album *>));
ModelsCollection<Album> expectedAlbums {
{{NAME, "album1"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album1"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 4}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 3}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album2"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album3"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 2}, {NOTE, ""}},
{{NAME, "album4"}, {SIZE_, 1}, {NOTE, ""}},
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "b"}}, // stable sort must guarantee this order
{{NAME, "album5"}, {SIZE_, 1}, {NOTE, "a"}},
};
QCOMPARE(sorted, expectedAlbums);
}
void tst_Collection_Relations::stableSortBy_Projection() const
{
ModelsCollection<Album> albums {

View File

@@ -12,6 +12,7 @@ namespace Models
using Orm::Constants::ID;
using Orm::Constants::NAME;
using Orm::Constants::NOTE;
using Orm::Constants::SIZE_;
using Orm::Tiny::Model;
using Orm::Tiny::Relations::HasMany;
@@ -48,6 +49,7 @@ private:
ID,
NAME,
NOTE,
SIZE_, // Database table doesn't contain the size column but I need it in tests
};
};