diff --git a/docs/supported-compilers.mdx b/docs/supported-compilers.mdx index 5de3ccf2d..128e9b915 100644 --- a/docs/supported-compilers.mdx +++ b/docs/supported-compilers.mdx @@ -8,7 +8,7 @@ keywords: [c++ orm, supported compilers, supported build systems, tinyorm] # Supported Compilers -Following compilers are backed up by the GitHub Action [workflows](https://github.com/silverqx/TinyORM/tree/main/.github/workflows) (CI pipelines), these workflows also include more then __1220 unit tests__ 😮💥. +Following compilers are backed up by the GitHub Action [workflows](https://github.com/silverqx/TinyORM/tree/main/.github/workflows) (CI pipelines), these workflows also include more then __1223 unit tests__ 😮💥.
diff --git a/include/orm/basegrammar.hpp b/include/orm/basegrammar.hpp index ec856fa45..d7b0c807a 100644 --- a/include/orm/basegrammar.hpp +++ b/include/orm/basegrammar.hpp @@ -79,6 +79,11 @@ namespace Query /*! Get the column name without the table name, a string after last dot. */ QString unqualifyColumn(const QString &column) const; + /*! Get the table name without an alias. */ + QString getFromWithoutAlias(const QString &from) const; + /*! Get an alias from the 'from' clause. */ + QString getAliasFromFrom(const QString &from) const; + protected: /*! Convert the vector of column names into a wrapped comma delimited string. */ template @@ -103,10 +108,6 @@ namespace Query /*! Get individual segments from the 'from' clause. */ QStringList getSegmentsFromFrom(const QString &from) const; - /*! Get the table name without an alias. */ - QString getFromWithoutAlias(const QString &from) const; - /*! Get an alias from the 'from' clause. */ - QString getAliasFromFrom(const QString &from) const; // FEATURE qt6, use everywhere QLatin1String("") instead of = "", BUT Qt6 has char8_t ctor, so u"" can be used, I will wait with this problem silverqx /*! The grammar table prefix. */ diff --git a/include/orm/query/querybuilder.hpp b/include/orm/query/querybuilder.hpp index e2b7eff3c..8b6c2c68a 100644 --- a/include/orm/query/querybuilder.hpp +++ b/include/orm/query/querybuilder.hpp @@ -693,6 +693,9 @@ namespace Orm::Query Builder cloneWithoutBindings( const std::unordered_set &except) const; + /*! Strip off the table name or alias from a column identifier. */ + QString stripTableForPluck(const QString &column) const; + protected: /*! Determine if the given operator is supported. */ bool invalidOperator(const QString &comparison) const; @@ -753,9 +756,6 @@ namespace Orm::Query /*! Prepend the database name if the given query is on another database. */ Builder &prependDatabaseNameIfCrossDatabaseQuery(Builder &query) const; - /*! Strip off the table name or alias from a column identifier. */ - QString stripTableForPluck(const QString &column) const; - /*! Throw an exception if the query doesn't have an orderBy clause. */ void enforceOrderBy() const; /*! Get an array with all orders with a given column removed. */ @@ -903,9 +903,9 @@ namespace Orm::Query if (size == 0) return {}; - /* If the columns are qualified with a table or have an alias, we cannot use - those directly in the "pluck" operations since the results from the DB - are only keyed by the column itself. We'll strip the table out here. */ + /* If the column is qualified with a table or have an alias, we cannot use + those directly in the "pluck" operations, we have to strip the table out or + use the alias name instead. */ const auto unqualifiedColumn = stripTableForPluck(column); const auto unqualifiedKey = stripTableForPluck(key); diff --git a/include/orm/tiny/tinybuilder.hpp b/include/orm/tiny/tinybuilder.hpp index a769b9b9a..fd81b4dc0 100644 --- a/include/orm/tiny/tinybuilder.hpp +++ b/include/orm/tiny/tinybuilder.hpp @@ -7,6 +7,7 @@ TINY_SYSTEM_HEADER #include +#include #include #include #include @@ -73,10 +74,10 @@ namespace Orm::Tiny QVariant soleValue(const Column &column); /*! Get the vector with the values of a given column. */ - QVector pluck(const QString &column) const; + QVector pluck(const QString &column); /*! Get the vector with the values of a given column. */ template - std::map pluck(const QString &column, const QString &key) const; + std::map pluck(const QString &column, const QString &key); /*! Find a model by its primary key. */ std::optional @@ -335,18 +336,61 @@ namespace Orm::Tiny } template - QVector Builder::pluck(const QString &column) const + QVector Builder::pluck(const QString &column) { - // CUR now, use newFromBuilder() to make and fill models and return attribute/column silverqx - return toBase().pluck(column); + auto result = toBase().pluck(column); + + // Nothing to pluck-ing 😎 + if (result.empty()) + return result; + + /* If the column is qualified with a table or have an alias, we cannot use + those directly in the "pluck" operations, we have to strip the table out or + use the alias name instead. */ + const auto unqualifiedColumn = getQuery().stripTableForPluck(column); + + /* If the model has a mutator for the requested column, we will spin through + the results and mutate the values so that the mutated version of these + columns are returned as you would expect from these Eloquent models. */ + if (!m_model.getDates().contains(unqualifiedColumn)) + return result; + + return result |= ranges::actions::transform([this, &unqualifiedColumn] + (auto &&value) + { + return m_model.newFromBuilder({{unqualifiedColumn, + std::forward(value)}}) + .getAttribute(unqualifiedColumn); + }); } template template std::map - Builder::pluck(const QString &column, const QString &key) const + Builder::pluck(const QString &column, const QString &key) { - return toBase().template pluck(column, key); + auto result = toBase().template pluck(column, key); + + // Nothing to pluck-ing 😎 + if (result.empty()) + return result; + + /* If the column is qualified with a table or have an alias, we cannot use + those directly in the "pluck" operations, we have to strip the table out or + use the alias name instead. */ + const auto unqualifiedColumn = getQuery().stripTableForPluck(column); + + /* If the model has a mutator for the requested column, we will spin through + the results and mutate the values so that the mutated version of these + columns are returned as you would expect from these Eloquent models. */ + if (!m_model.getDates().contains(unqualifiedColumn)) + return result; + + for (auto &&[_, value] : result) + value = m_model.newFromBuilder({{unqualifiedColumn, std::move(value)}}) + .getAttribute(unqualifiedColumn); + + return result; } // FEATURE dilemma primarykey, Model::KeyType for id silverqx diff --git a/src/orm/basegrammar.cpp b/src/orm/basegrammar.cpp index d55c0a49e..24f4d2c69 100644 --- a/src/orm/basegrammar.cpp +++ b/src/orm/basegrammar.cpp @@ -118,6 +118,16 @@ QString BaseGrammar::unqualifyColumn(const QString &column) const return column.split(DOT).last().trimmed(); } +QString BaseGrammar::getFromWithoutAlias(const QString &from) const +{ + return std::move(getSegmentsFromFrom(from).first()); // clazy:exclude=detaching-temporary +} + +QString BaseGrammar::getAliasFromFrom(const QString &from) const +{ + return std::move(getSegmentsFromFrom(from).last()); // clazy:exclude=detaching-temporary +} + /* protected */ QString BaseGrammar::parameter(const QVariant &value) const @@ -177,22 +187,6 @@ QStringList BaseGrammar::getSegmentsFromFrom(const QString &from) const return segments; } -QString BaseGrammar::getFromWithoutAlias(const QString &from) const -{ - // Prevent clazy warning - const auto segments = getSegmentsFromFrom(from); - - return segments.first(); -} - -QString BaseGrammar::getAliasFromFrom(const QString &from) const -{ - // Prevent clazy warning - const auto segments = getSegmentsFromFrom(from); - - return segments.last(); -} - } // namespace Orm TINYORM_END_COMMON_NAMESPACE diff --git a/src/orm/query/querybuilder.cpp b/src/orm/query/querybuilder.cpp index f303b856f..cf3f10536 100644 --- a/src/orm/query/querybuilder.cpp +++ b/src/orm/query/querybuilder.cpp @@ -113,9 +113,9 @@ QVector Builder::pluck(const QString &column) if (size == 0) return {}; - /* If the columns are qualified with a table or have an alias, we cannot use - those directly in the "pluck" operations since the results from the DB - are only keyed by the column itself. We'll strip the table out here. */ + /* If the column is qualified with a table or have an alias, we cannot use + those directly in the "pluck" operations, we have to strip the table out or + use the alias name instead. */ const auto unqualifiedColumn = stripTableForPluck(column); QVector result; @@ -1125,6 +1125,16 @@ Builder Builder::cloneWithoutBindings( return copy; } +QString Builder::stripTableForPluck(const QString &column) const +{ + static const auto as = QStringLiteral(" as "); + + if (!column.contains(as)) + return m_grammar.unqualifyColumn(column); + + return m_grammar.getAliasFromFrom(column); +} + /* protected */ bool Builder::invalidOperator(const QString &comparison) const @@ -1279,16 +1289,6 @@ Builder &Builder::prependDatabaseNameIfCrossDatabaseQuery(Builder &query) const return query; } -QString Builder::stripTableForPluck(const QString &column) const -{ - static const auto as = QStringLiteral(" as "); - - if (!column.contains(as)) - return m_grammar.unqualifyColumn(column); - - return column.split(as).last().trimmed(); -} - void Builder::enforceOrderBy() const { if (m_orders.isEmpty()) diff --git a/tests/auto/functional/orm/tiny/model_conn_indep/tst_model_connection_independent.cpp b/tests/auto/functional/orm/tiny/model_conn_indep/tst_model_connection_independent.cpp index 119f3fe1e..09cf45055 100644 --- a/tests/auto/functional/orm/tiny/model_conn_indep/tst_model_connection_independent.cpp +++ b/tests/auto/functional/orm/tiny/model_conn_indep/tst_model_connection_independent.cpp @@ -66,6 +66,10 @@ private slots: void pluck_EmptyResult() const; void pluck_QualifiedColumnOrKey() const; + void pluck_With_u_dates() const; + void pluck_EmptyResult_With_u_dates() const; + void pluck_QualifiedColumnOrKey_With_u_dates() const; + /* Builds Queries */ void chunk() const; void chunk_ReturnFalse() const; @@ -439,15 +443,15 @@ void tst_Model_Connection_Independent::pluck() const QCOMPARE(result, expected); } // Templated pluck keyed by file_index, bool type is used intentionally 😎 -// { -// auto result = TorrentPreviewableFile::orderBy(ID) -// ->pluck("filepath", "file_index"); - -// std::map expected { -// {false, "test1_file1.mkv"}, {true, "test2_file2.mkv"}, -// }; -// QCOMPARE(result, expected); -// } + { + auto result = TorrentPreviewableFile::orderBy(ID) + ->pluck("filepath", "file_index"); + + std::map expected { + {false, "test1_file1.mkv"}, {true, "test2_file2.mkv"}, + }; + QCOMPARE(result, expected); + } } void tst_Model_Connection_Independent::pluck_EmptyResult() const @@ -518,6 +522,142 @@ void tst_Model_Connection_Independent::pluck_QualifiedColumnOrKey() const } } +void tst_Model_Connection_Independent::pluck_With_u_dates() const +{ + // Simple pluck without keying + { + auto result = Torrent::pluck("added_on"); + + QVector expected { + QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate), + }; + QCOMPARE(result, expected); + } + // Templated pluck keyed by id and QVariant as the value + { + auto result = Torrent::pluck("added_on", ID); + + std::map expected { + {1, QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate)}, + {2, QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate)}, + {3, QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate)}, + {4, QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate)}, + {5, QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate)}, + {6, QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate)}, + }; + QCOMPARE(result, expected); + } + // Templated pluck keyed by added_on + { + auto result = Torrent::pluck(ID, "added_on"); + + std::map expected { + {QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate), 1}, + {QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate), 2}, + {QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate), 3}, + {QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate), 4}, + {QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate), 5}, + {QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate), 6}, + }; + QCOMPARE(result, expected); + } +} + +void tst_Model_Connection_Independent::pluck_EmptyResult_With_u_dates() const +{ + { + auto result = Torrent::whereEq(NAME, "dummy-NON_EXISTENT")->pluck("added_on"); + + QCOMPARE(result, QVector()); + } + { + auto result = Torrent::whereEq(NAME, "dummy-NON_EXISTENT") + ->pluck(ID, "added_on"); + + std::map expected; + QCOMPARE(result, expected); + } +} + +void tst_Model_Connection_Independent::pluck_QualifiedColumnOrKey_With_u_dates() const +{ + // Strip table name + { + auto result = Torrent::pluck("torrents.added_on"); + + QVector expected { + QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate), + }; + QCOMPARE(result, expected); + } + // Strip table name + { + auto result = Torrent::pluck("torrents.added_on", ID); + + std::map expected { + {1, QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate)}, + {2, QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate)}, + {3, QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate)}, + {4, QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate)}, + {5, QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate)}, + {6, QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate)}, + }; + QCOMPARE(result, expected); + } + // Strip column alias + { + auto result = Torrent::orderBy(ID)->pluck("added_on as added_on_alt"); + + QVector expected { + QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate), + QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate), + }; + QCOMPARE(result, expected); + } + // Strip column alias + { + auto result = Torrent::pluck("added_on as added_on_alt", "id as id_alt"); + + std::map expected { + {1, QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate)}, + {2, QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate)}, + {3, QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate)}, + {4, QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate)}, + {5, QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate)}, + {6, QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate)}, + }; + QCOMPARE(result, expected); + } + // Strip column alias and table name + { + auto result = Torrent::pluck("torrents.added_on", "id as id_alt"); + + std::map expected { + {1, QDateTime::fromString("2020-08-01 20:11:10", Qt::ISODate)}, + {2, QDateTime::fromString("2020-08-02 20:11:10", Qt::ISODate)}, + {3, QDateTime::fromString("2020-08-03 20:11:10", Qt::ISODate)}, + {4, QDateTime::fromString("2020-08-04 20:11:10", Qt::ISODate)}, + {5, QDateTime::fromString("2020-08-05 20:11:10", Qt::ISODate)}, + {6, QDateTime::fromString("2020-08-06 20:11:10", Qt::ISODate)}, + }; + QCOMPARE(result, expected); + } +} + /* Builds Queries */ void tst_Model_Connection_Independent::chunk() const diff --git a/tests/models/models/torrent.hpp b/tests/models/models/torrent.hpp index e4a0055ef..b8dbfc86e 100644 --- a/tests/models/models/torrent.hpp +++ b/tests/models/models/torrent.hpp @@ -185,7 +185,7 @@ class Torrent final : // inline static QString u_dateFormat {"yyyy-MM-dd HH:mm:ss"}; /*! The attributes that should be mutated to dates. */ - inline static const QStringList u_dates {"added_on"}; + inline static const QStringList u_dates {"added_on", "added_on_alt"}; /*! All of the relationships to be touched. */ // QStringList u_touches {"tags"};