#pragma once #ifndef TINYBUILDER_H #define TINYBUILDER_H #include #include #include #include #include "orm/databaseconnection.hpp" #include "orm/query/querybuilder.hpp" #include "orm/tiny/modelnotfounderror.hpp" #include "orm/utils/attribute.hpp" #include "orm/utils/type.hpp" #ifdef TINYORM_COMMON_NAMESPACE namespace TINYORM_COMMON_NAMESPACE { #endif namespace Orm::Tiny { namespace Relations { template class Relation; } template class Builder { using JoinClause = Orm::Query::JoinClause; public: Builder(const QSharedPointer query, Model &model); /*! Get the SQL representation of the query. */ QString toSql() const; /*! Get the current query value bindings as flattened QVector. */ QVector getBindings() const; /*! Execute the query as a "select" statement. */ QVector get(const QVector &columns = {ASTERISK}); /*! Get a single column's value from the first result of a query. */ QVariant value(const Column &column); /*! Get the vector with the values of a given column. */ QVector pluck(const QString &column) const; /*! Get the vector with the values of a given column. */ template std::map pluck(const QString &column, const QString &key) const; /*! Find a model by its primary key. */ std::optional find(const QVariant &id, const QVector &columns = {ASTERISK}); /*! Find a model by its primary key or return fresh model instance. */ Model findOrNew(const QVariant &id, const QVector &columns = {ASTERISK}); /*! Find a model by its primary key or throw an exception. */ Model findOrFail(const QVariant &id, const QVector &columns = {ASTERISK}); /*! Find multiple models by their primary keys. */ QVector findMany(const QVector &ids, const QVector &columns = {ASTERISK}); /*! Execute the query and get the first result. */ std::optional first(const QVector &columns = {ASTERISK}); /*! Get the first record matching the attributes or instantiate it. */ Model firstOrNew(const QVector &attributes = {}, const QVector &values = {}); /*! Get the first record matching the attributes or create it. */ Model firstOrCreate(const QVector &attributes = {}, const QVector &values = {}); /*! Execute the query and get the first result or throw an exception. */ Model firstOrFail(const QVector &columns = {ASTERISK}); /*! Add a basic where clause to the query, and return the first result. */ std::optional firstWhere(const Column &column, const QString &comparison, const QVariant &value, const QString &condition = AND); /*! Add a basic equal where clause to the query, and return the first result. */ std::optional firstWhereEq(const Column &column, const QVariant &value, const QString &condition = AND); /*! Add a where clause on the primary key to the query. */ Builder &whereKey(const QVariant &id); /*! Add a where clause on the primary key to the query. */ Builder &whereKey(const QVector &ids); /*! Add a where clause on the primary key to the query. */ Builder &whereKeyNot(const QVariant &id); /*! Add a where clause on the primary key to the query. */ Builder &whereKeyNot(const QVector &ids); /*! Set the relationships that should be eager loaded. */ template Builder &with(const QVector &relations); /*! Set the relationships that should be eager loaded. */ template Builder &with(const QString &relation); /*! Set the relationships that should be eager loaded. */ Builder &with(const QVector &relations); /*! Set the relationships that should be eager loaded. */ Builder &with(QVector &&relations); /*! Prevent the specified relations from being eager loaded. */ Builder &without(const QVector &relations); /*! Prevent the specified relations from being eager loaded. */ Builder &without(const QString &relation); /*! Set the relationships that should be eager loaded while removing any previously added eager loading specifications. */ Builder &withOnly(const QVector &relations); /*! Set the relationship that should be eager loaded while removing any previously added eager loading specifications. */ Builder &withOnly(const QString &relation); /* Insert, Update, Delete */ /*! Save a new model and return the instance. */ Model create(const QVector &attributes = {}); /*! Save a new model and return the instance. */ Model create(QVector &&attributes = {}); /*! Create or update a record matching the attributes, and fill it with values. */ Model updateOrCreate(const QVector &attributes, const QVector &values = {}); /* Proxy methods to the QueryBuilder */ /* Insert, Update, Delete */ /*! Insert a new record into the database. */ std::optional insert(const QVector &values) const; /*! Insert new records into the database. */ std::optional insert(const QVector> &values) const; /*! Insert a new record and get the value of the primary key. */ quint64 insertGetId(const QVector &values, const QString &sequence = "") const; /*! Insert a new record into the database while ignoring errors. */ std::tuple> insertOrIgnore(const QVector &values) const; /*! Insert new records into the database while ignoring errors. */ std::tuple> insertOrIgnore(const QVector> &values) const; /*! Update records in the database. */ std::tuple update(const QVector &values) const; /*! Delete records from the database. */ std::tuple remove() const; /*! Delete records from the database. */ std::tuple deleteModels() const; /*! Run a truncate statement on the table. */ void truncate() const; /* Select */ /*! Retrieve the "count" result of the query. */ quint64 count(const QVector &columns = {ASTERISK}) const; /*! Retrieve the "count" result of the query. */ template quint64 count(const Column &column) const; /*! Retrieve the minimum value of a given column. */ QVariant min(const Column &column) const; /*! Retrieve the maximum value of a given column. */ QVariant max(const Column &column) const; /*! Retrieve the sum of the values of a given column. */ QVariant sum(const Column &column) const; /*! Retrieve the average of the values of a given column. */ QVariant avg(const Column &column) const; /*! Alias for the "avg" method. */ QVariant average(const Column &column) const; /*! Execute an aggregate function on the database. */ QVariant aggregate(const QString &function, const QVector &columns = {ASTERISK}) const; /*! Set the columns to be selected. */ Builder &select(const QVector &columns = {ASTERISK}); /*! Set the column to be selected. */ Builder &select(const Column &column); /*! Add new select columns to the query. */ Builder &addSelect(const QVector &columns); /*! Add a new select column to the query. */ Builder &addSelect(const Column &column); /*! Add a subselect expression to the query. */ template Builder &selectSub(T &&query, const QString &as); /*! Add a new "raw" select expression to the query. */ Builder &selectRaw(const QString &expression, const QVector &bindings = {}); /*! Force the query to only return distinct results. */ Builder &distinct(); /*! Force the query to only return distinct results. */ Builder &distinct(const QStringList &columns); /*! Force the query to only return distinct results. */ Builder &distinct(QStringList &&columns); /*! Add a join clause to the query. */ template Builder &join(T &&table, const QString &first, const QString &comparison, const QString &second, const QString &type = INNER, bool where = false); /*! Add an advanced join clause to the query. */ template Builder &join(T &&table, const std::function &callback, const QString &type = INNER); /*! Add a "join where" clause to the query. */ template Builder &joinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second, const QString &type = INNER); /*! Add a left join to the query. */ template Builder &leftJoin(T &&table, const QString &first, const QString &comparison, const QString &second); /*! Add an advanced left join to the query. */ template Builder &leftJoin(T &&table, const std::function &callback); /*! Add a "join where" clause to the query. */ template Builder &leftJoinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second); /*! Add a right join to the query. */ template Builder &rightJoin(T &&table, const QString &first, const QString &comparison, const QString &second); /*! Add an advanced right join to the query. */ template Builder &rightJoin(T &&table, const std::function &callback); /*! Add a "right join where" clause to the query. */ template Builder &rightJoinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second); /*! Add a "cross join" clause to the query. */ template Builder &crossJoin(T &&table, const QString &first, const QString &comparison, const QString &second); /*! Add an advanced "cross join" clause to the query. */ template Builder &crossJoin(T &&table, const std::function &callback); /*! Add a subquery join clause to the query. */ template Builder &joinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second, const QString &type = INNER, bool where = false); /*! Add a subquery join clause to the query. */ template Builder &joinSub(T &&query, const QString &as, const std::function &callback, const QString &type = INNER); /*! Add a subquery left join to the query. */ template Builder &leftJoinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second); /*! Add a subquery left join to the query. */ template Builder &leftJoinSub(T &&query, const QString &as, const std::function &callback); /*! Add a subquery right join to the query. */ template Builder &rightJoinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second); /*! Add a subquery right join to the query. */ template Builder &rightJoinSub(T &&query, const QString &as, const std::function &callback); /*! Add a basic where clause to the query. */ template Builder &where(const Column &column, const QString &comparison, T &&value, const QString &condition = AND); /*! Add an "or where" clause to the query. */ template Builder &orWhere(const Column &column, const QString &comparison, T &&value); /*! Add a basic equal where clause to the query. */ template Builder &whereEq(const Column &column, T &&value, const QString &condition = AND); /*! Add an equal "or where" clause to the query. */ template Builder &orWhereEq(const Column &column, T &&value); /*! Add a nested where clause to the query. */ Builder &where(const std::function &callback, const QString &condition = AND); /*! Add a nested "or where" clause to the query. */ Builder &orWhere(const std::function &callback); /*! Add a vector of basic where clauses to the query. */ Builder &where(const QVector &values, const QString &condition = AND); /*! Add a vector of basic "or where" clauses to the query. */ Builder &orWhere(const QVector &values); /*! Add a vector of where clauses comparing two columns to the query. */ Builder &whereColumn(const QVector &values, const QString &condition = AND); /*! Add a vector of "or where" clauses comparing two columns to the query. */ Builder &orWhereColumn(const QVector &values); /*! Add a "where" clause comparing two columns to the query. */ Builder &whereColumn(const Column &first, const QString &comparison, const Column &second, const QString &condition = AND); /*! Add a "or where" clause comparing two columns to the query. */ Builder &orWhereColumn(const Column &first, const QString &comparison, const Column &second); /*! Add an equal "where" clause comparing two columns to the query. */ Builder &whereColumnEq(const Column &first, const Column &second, const QString &condition = AND); /*! Add an equal "or where" clause comparing two columns to the query. */ Builder &orWhereColumnEq(const Column &first, const Column &second); /*! Add a "where in" clause to the query. */ Builder &whereIn(const Column &column, const QVector &values, const QString &condition = AND, bool nope = false); /*! Add an "or where in" clause to the query. */ Builder &orWhereIn(const Column &column, const QVector &values); /*! Add a "where not in" clause to the query. */ Builder &whereNotIn(const Column &column, const QVector &values, const QString &condition = AND); /*! Add an "or where not in" clause to the query. */ Builder &orWhereNotIn(const Column &column, const QVector &values); /*! Add a "where null" clause to the query. */ Builder &whereNull(const QVector &columns = {ASTERISK}, const QString &condition = AND, bool nope = false); /*! Add an "or where null" clause to the query. */ Builder &orWhereNull(const QVector &columns = {ASTERISK}); /*! Add a "where not null" clause to the query. */ Builder &whereNotNull(const QVector &columns = {ASTERISK}, const QString &condition = AND); /*! Add an "or where not null" clause to the query. */ Builder &orWhereNotNull(const QVector &columns = {ASTERISK}); /*! Add a "where null" clause to the query. */ Builder &whereNull(const Column &column, const QString &condition = AND, bool nope = false); /*! Add an "or where null" clause to the query. */ Builder &orWhereNull(const Column &column); /*! Add a "where not null" clause to the query. */ Builder &whereNotNull(const Column &column, const QString &condition = AND); /*! Add an "or where not null" clause to the query. */ Builder &orWhereNotNull(const Column &column); /*! Add a basic where clause to the query with a full sub-select column. */ template Builder &where(C &&column, const QString &comparison, V &&value, const QString &condition = AND); /*! Add an "or where" clause to the query with a full sub-select column. */ template Builder &orWhere(C &&column, const QString &comparison, V &&value); /*! Add a basic equal where clause to the query with a full sub-select column. */ template Builder &whereEq(C &&column, V &&value, const QString &condition = AND); /*! Add an equal "or where" clause to the query with a full sub-select column. */ template Builder &orWhereEq(C &&column, V &&value); /*! Add a full sub-select to the "where" clause. */ template Builder &whereSub(const Column &column, const QString &comparison, T &&query, const QString &condition = AND); /*! Add a raw "where" clause to the query. */ Builder &whereRaw(const QString &sql, const QVector &bindings = {}, const QString &condition = AND); /*! Add a raw "or where" clause to the query. */ Builder &orWhereRaw(const QString &sql, const QVector &bindings = {}); /*! Add a "group by" clause to the query. */ Builder &groupBy(const QVector &groups); /*! Add a "group by" clause to the query. */ Builder &groupBy(const Column &group); /*! Add a "group by" clause to the query. */ template Builder &groupBy(Args &&...groups); /*! Add a raw "groupBy" clause to the query. */ Builder &groupByRaw(const QString &sql, const QVector &bindings = {}); /*! Add a "having" clause to the query. */ Builder &having(const Column &column, const QString &comparison, const QVariant &value, const QString &condition = AND); /*! Add an "or having" clause to the query. */ Builder &orHaving(const Column &column, const QString &comparison, const QVariant &value); /*! Add a raw "having" clause to the query. */ Builder &havingRaw(const QString &sql, const QVector &bindings = {}, const QString &condition = AND); /*! Add a raw "or having" clause to the query. */ Builder &orHavingRaw(const QString &sql, const QVector &bindings = {}); /*! Add an "order by" clause to the query. */ Builder &orderBy(const Column &column, const QString &direction = ASC); /*! Add a descending "order by" clause to the query. */ Builder &orderByDesc(const Column &column); /*! Add a raw "order by" clause to the query. */ Builder &orderByRaw(const QString &sql, const QVector &bindings = {}); /*! Add an "order by" clause for a timestamp to the query. */ Builder &latest(const Column &column = ""); /*! Add an "order by" clause for a timestamp to the query. */ Builder &oldest(const Column &column = ""); /*! Remove all existing orders. */ Builder &reorder(); /*! Remove all existing orders and optionally add a new order. */ Builder &reorder(const Column &column, const QString &direction = ASC); /*! Set the "limit" value of the query. */ Builder &limit(int value); /*! Alias to set the "limit" value of the query. */ Builder &take(int value); /*! Set the "offset" value of the query. */ Builder &offset(int value); /*! Alias to set the "offset" value of the query. */ Builder &skip(int value); /*! Set the limit and offset for a given page. */ Builder &forPage(int page, int perPage = 30); /*! Increment a column's value by a given amount. */ template requires std::is_arithmetic_v std::tuple increment(const QString &column, T amount = 1, const QVector &extra = {}) const; /*! Decrement a column's value by a given amount. */ template requires std::is_arithmetic_v std::tuple decrement(const QString &column, T amount = 1, const QVector &extra = {}) const; /* Pessimistic Locking */ /*! Lock the selected rows in the table for updating. */ Builder &lockForUpdate(); /*! Share lock the selected rows in the table. */ Builder &sharedLock(); /*! Lock the selected rows in the table. */ Builder &lock(bool value = true); /*! Lock the selected rows in the table. */ Builder &lock(const char *value); /*! Lock the selected rows in the table. */ Builder &lock(const QString &value); /*! Lock the selected rows in the table. */ Builder &lock(QString &&value); /* TinyBuilder methods */ /*! Create a new instance of the model being queried. */ Model newModelInstance(const QVector &attributes = {}); /*! Get the hydrated models without eager loading. */ QVector getModels(const QVector &columns = {ASTERISK}); /*! Eager load the relationships for the models. */ void eagerLoadRelations(QVector &models); /*! Eagerly load the relationship on a set of models. */ template void eagerLoadRelationVisited(Relation &&relation, QVector &models, const WithItem &relationItem) const; /*! Create a vector of models from the QSqlQuery. */ QVector hydrate(QSqlQuery &&result); /*! Get the model instance being queried. */ inline Model &getModel() { return m_model; } /*! Get the underlying query builder instance. */ inline QueryBuilder &getQuery() const { return *m_query; } // TODO now fix revisit silverqx /*! Get the underlying query builder instance as a QSharedPointer. */ inline const QSharedPointer & getQuerySharedPointer() const { return m_query; } /*! Get a database connection. */ inline const ConnectionInterface &getConnection() const { return m_query->getConnection(); } /*! Get a base query builder instance. */ inline QueryBuilder &toBase() const { return getQuery(); } // FUTURE add Query Scopes feature silverqx // { return $this->applyScopes()->getQuery(); } protected: /*! Expression alias. */ using Expression = Orm::Query::Expression; /*! Parse a list of relations into individuals. */ QVector parseWithRelations(const QVector &relations); /*! Create a constraint to select the given columns for the relation. */ WithItem createSelectWithConstraint(const QString &name); /*! Parse the nested relationships in a relation. */ void addNestedWiths(const QString &name, QVector &results) const; /*! Get the deeply nested relations for a given top-level relation. */ QVector relationsNestedUnder(const QString &topRelationName) const; /*! Determine if the relationship is nested. */ bool isNestedUnder(const QString &topRelation, const QString &nestedRelation) const; /*! Add the "updated at" column to the vector of values. */ QVector addUpdatedAtColumn(QVector values) const; /*! Get the name of the "created at" column. */ Column getCreatedAtColumnForLatestOldest(Column column) const; /*! The base query builder instance. */ const QSharedPointer m_query; /* This can't be a reference because the model is created on the stack in Model::query(), then copied here and the original is destroyed immediately. */ /*! The model being queried. */ Model m_model; /*! The relationships that should be eager loaded. */ QVector m_eagerLoad; }; template Builder::Builder(const QSharedPointer query, Model &model) : m_query(query) , m_model(model) { m_query->from(m_model.getTable()); } template inline QString Builder::toSql() const { return toBase().toSql(); } template inline QVector Builder::getBindings() const { return toBase().getBindings(); } // TODO now name QVector model collections by using, eg CollectionType silverqx template QVector Builder::get(const QVector &columns) { auto models = getModels(columns); /* If we actually found models we will also eager load any relationships that have been specified as needing to be eager loaded, which will solve the n+1 query issue for the developers to avoid running a lot of queries. */ if (models.size() > 0) /* 'models' are passed down as the reference and relations are set on models at the end of the call tree, no need to return models. */ eagerLoadRelations(models); return models; // Laravel does it this way // return $builder->getModel()->newCollection($models); } template QVariant Builder::value(const Column &column) { auto model = first({column}); if (!model) return {}; // Expression support QString column_; if (std::holds_alternative(column)) column_ = std::get(column).getValue().value(); else column_ = std::get(column); return model->getAttribute(column_.mid(column_.lastIndexOf(DOT) + 1)); } template QVector Builder::pluck(const QString &column) const { return toBase().pluck(column); } template template std::map Builder::pluck(const QString &column, const QString &key) const { return toBase().template pluck(column, key); } // FEATURE dilemma primarykey, Model::KeyType for id silverqx template std::optional Builder::find(const QVariant &id, const QVector &columns) { return whereKey(id).first(columns); } template Model Builder::findOrNew(const QVariant &id, const QVector &columns) { auto model = find(id, columns); // Found if (model) return *model; return newModelInstance(); } template Model Builder::findOrFail(const QVariant &id, const QVector &columns) { auto model = find(id, columns); // Found if (model) return *model; throw ModelNotFoundError(Utils::Type::classPureBasename(), {id}); } template QVector Builder::findMany(const QVector &ids, const QVector &columns) { if (ids.isEmpty()) return {}; return whereKey(ids).get(columns); } template std::optional Builder::first(const QVector &columns) { auto models = take(1).get(columns); if (models.isEmpty()) return std::nullopt; return std::move(models.first()); } template Model Builder::firstOrNew(const QVector &attributes, const QVector &values) { auto instance = where(attributes).first(); // Model found in db if (instance) return *instance; return newModelInstance( Utils::Attribute::joinAttributesForFirstOr(attributes, values, m_model.getKeyName())); } template Model Builder::firstOrCreate(const QVector &attributes, const QVector &values) { // Model found in db if (auto instance = where(attributes).first(); instance) return *instance; auto newInstance = newModelInstance( Utils::Attribute::joinAttributesForFirstOr(attributes, values, m_model.getKeyName())); newInstance.save(); return newInstance; } template Model Builder::firstOrFail(const QVector &columns) { auto model = first(columns); // Found if (model) return *model; throw ModelNotFoundError(Utils::Type::classPureBasename()); } template std::optional Builder::firstWhere(const Column &column, const QString &comparison, const QVariant &value, const QString &condition) { return where(column, comparison, value, condition).first(); } template std::optional Builder::firstWhereEq(const Column &column, const QVariant &value, const QString &condition) { return where(column, EQ, value, condition).first(); } template Builder & Builder::whereKey(const QVariant &id) { return where(m_model.getQualifiedKeyName(), EQ, id); } template Builder & Builder::whereKey(const QVector &ids) { m_query->whereIn(m_model.getQualifiedKeyName(), ids); return *this; } template Builder & Builder::whereKeyNot(const QVariant &id) { return where(m_model.getQualifiedKeyName(), NE, id); } template Builder & Builder::whereKeyNot(const QVector &ids) { m_query->whereNotIn(m_model.getQualifiedKeyName(), ids); return *this; } template template Builder & Builder::with(const QVector &relations) { auto eagerLoad = parseWithRelations(relations); std::ranges::move(eagerLoad, std::back_inserter(m_eagerLoad)); return *this; } template template Builder & Builder::with(const QString &relation) { return with(QVector {{relation}}); } template Builder & Builder::with(const QVector &relations) { QVector relationsConverted; relationsConverted.reserve(relations.size()); for (const auto &relation : relations) relationsConverted.append({relation}); return with(relationsConverted); } template Builder & Builder::with(QVector &&relations) { QVector relationsConverted; relationsConverted.reserve(relations.size()); for (auto &relation : relations) relationsConverted.append({std::move(relation)}); return with(relationsConverted); } template Builder & Builder::without(const QVector &relations) { // Remove relations in the "relations" vector from m_eagerLoad vector using namespace ranges; m_eagerLoad = m_eagerLoad | views::remove_if( [&relations](const WithItem &with) { return relations.contains(with.name); }) | ranges::to>(); return *this; } template Builder & Builder::without(const QString &relation) { return without(QVector {relation}); } template Builder & Builder::withOnly(const QVector &relations) { m_eagerLoad.clear(); return with(relations); } template Builder & Builder::withOnly(const QString &relation) { return withOnly(QVector {{relation}}); } template Model Builder::create(const QVector &attributes) { auto model = newModelInstance(attributes); model.save(); return model; } template Model Builder::create(QVector &&attributes) { auto model = newModelInstance(std::move(attributes)); model.save(); return model; } template Model Builder::updateOrCreate(const QVector &attributes, const QVector &values) { auto instance = firstOrNew(attributes); instance.fill(values).save(); return instance; } template std::optional Builder::insert(const QVector &values) const { return toBase().insert(Utils::Attribute::convertVectorToMap(values)); } template std::optional Builder::insert(const QVector> &values) const { return toBase().insert(Utils::Attribute::convertVectorsToMaps(values)); } // FEATURE dilemma primarykey, Model::KeyType vs QVariant silverqx template quint64 Builder::insertGetId(const QVector &values, const QString &sequence) const { return toBase().insertGetId(Utils::Attribute::convertVectorToMap(values), sequence); } template std::tuple> Builder::insertOrIgnore(const QVector &values) const { return toBase().insertOrIgnore(Utils::Attribute::convertVectorToMap(values)); } template std::tuple> Builder::insertOrIgnore(const QVector> &values) const { return toBase().insertOrIgnore(Utils::Attribute::convertVectorsToMaps(values)); } template std::tuple Builder::update(const QVector &values) const { return toBase().update(addUpdatedAtColumn(values)); } // FUTURE add onDelete (and similar) callback feature silverqx template std::tuple Builder::remove() const { return toBase().deleteRow(); } template std::tuple Builder::deleteModels() const { return remove(); } template void Builder::truncate() const { toBase().truncate(); } template quint64 Builder::count(const QVector &columns) const { return toBase().count(columns); } template template quint64 Builder::count(const Column &column) const { return toBase().count(QVector {column}); } template QVariant Builder::min(const Column &column) const { return toBase().min(column); } template QVariant Builder::max(const Column &column) const { return toBase().max(column); } template QVariant Builder::sum(const Column &column) const { return toBase().sum(column); } template QVariant Builder::avg(const Column &column) const { return toBase().avg(column); } template QVariant Builder::average(const Column &column) const { return toBase().avg(column); } template QVariant Builder::aggregate(const QString &function, const QVector &columns) const { return toBase().aggregate(function, columns); } template Builder &Builder::select(const QVector &columns) { toBase().select(columns); return *this; } template Builder &Builder::select(const Column &column) { toBase().select(column); return *this; } template Builder &Builder::addSelect(const QVector &columns) { toBase().addSelect(columns); return *this; } template Builder &Builder::addSelect(const Column &column) { toBase().addSelect(column); return *this; } template template Builder &Builder::selectSub(T &&query, const QString &as) { toBase().selectSub(std::forward(query), as); return *this; } template Builder &Builder::selectRaw(const QString &expression, const QVector &bindings) { toBase().selectRaw(expression, bindings); return *this; } template Builder &Builder::distinct() { toBase().distinct(); return *this; } template Builder &Builder::distinct(const QStringList &columns) { toBase().distinct(columns); return *this; } template Builder &Builder::distinct(QStringList &&columns) { toBase().distinct(std::move(columns)); return *this; } template template Builder & Builder::join(T &&table, const QString &first, const QString &comparison, const QString &second, const QString &type, const bool where) { toBase().join(std::forward(table), first, comparison, second, type, where); return *this; } template template Builder & Builder::join(T &&table, const std::function &callback, const QString &type) { toBase().join(std::forward(table), callback, type); return *this; } template template Builder & Builder::joinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second, const QString &type) { toBase().joinWhere(std::forward(table), first, comparison, second, type); return *this; } template template Builder & Builder::leftJoin(T &&table, const QString &first, const QString &comparison, const QString &second) { toBase().leftJoin(std::forward(table), first, comparison, second); return *this; } template template Builder & Builder::leftJoin(T &&table, const std::function &callback) { toBase().leftJoin(std::forward(table), callback); return *this; } template template Builder & Builder::leftJoinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second) { toBase().leftJoinWhere(std::forward(table), first, comparison, second); return *this; } template template Builder & Builder::rightJoin(T &&table, const QString &first, const QString &comparison, const QString &second) { toBase().rightJoin(std::forward(table), first, comparison, second); return *this; } template template Builder & Builder::rightJoin(T &&table, const std::function &callback) { toBase().rightJoin(std::forward(table), callback); return *this; } template template Builder & Builder::rightJoinWhere(T &&table, const QString &first, const QString &comparison, const QVariant &second) { toBase().rightJoinWhere(std::forward(table), first, comparison, second); return *this; } template template Builder & Builder::crossJoin(T &&table, const QString &first, const QString &comparison, const QString &second) { toBase().crossJoin(std::forward(table), first, comparison, second); return *this; } template template Builder & Builder::crossJoin(T &&table, const std::function &callback) { toBase().crossJoin(std::forward(table), callback); return *this; } template template Builder & Builder::joinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second, const QString &type, const bool where) { toBase().joinSub(std::forward(query), as, first, comparison, second, type, where); return *this; } template template Builder & Builder::joinSub(T &&query, const QString &as, const std::function &callback, const QString &type) { toBase().joinSub(std::forward(query), as, callback, type); return *this; } template template Builder & Builder::leftJoinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second) { toBase().leftJoinSub(std::forward(query), as, first, comparison, second); return *this; } template template Builder & Builder::leftJoinSub(T &&query, const QString &as, const std::function &callback) { toBase().joinSub(std::forward(query), as, callback, LEFT); return *this; } template template Builder & Builder::rightJoinSub(T &&query, const QString &as, const QString &first, const QString &comparison, const QVariant &second) { toBase().rightJoinSub(std::forward(query), as, first, comparison, second); return *this; } template template Builder & Builder::rightJoinSub(T &&query, const QString &as, const std::function &callback) { toBase().joinSub(std::forward(query), as, callback, RIGHT); return *this; } template template Builder & Builder::where(const Column &column, const QString &comparison, T &&value, const QString &condition) { toBase().where(column, comparison, std::forward(value), condition); return *this; } template template Builder & Builder::orWhere(const Column &column, const QString &comparison, T &&value) { toBase().orWhere(column, comparison, std::forward(value)); return *this; } template template Builder & Builder::whereEq(const Column &column, T &&value, const QString &condition) { toBase().whereEq(column, std::forward(value), condition); return *this; } template template Builder & Builder::orWhereEq(const Column &column, T &&value) { toBase().orWhereEq(column, std::forward(value)); return *this; } template Builder & Builder::where(const std::function &callback, const QString &condition) { // Ownership of a unique_ptr() auto query = m_model.newQueryWithoutRelationships(); std::invoke(callback, *query); toBase().addNestedWhereQuery(query->getQuerySharedPointer(), condition); return *this; } template Builder & Builder::orWhere(const std::function &callback) { return where(callback, OR); } template Builder & Builder::where(const QVector &values, const QString &condition) { toBase().where(values, condition); return *this; } template Builder &Builder::orWhere(const QVector &values) { toBase().orWhere(values); return *this; } template Builder & Builder::whereColumn(const QVector &values, const QString &condition) { toBase().whereColumn(values, condition); return *this; } template Builder & Builder::orWhereColumn(const QVector &values) { toBase().orWhereColumn(values); return *this; } template Builder & Builder::whereColumn(const Column &first, const QString &comparison, const Column &second, const QString &condition) { toBase().whereColumn(first, comparison, second, condition); return *this; } template Builder & Builder::orWhereColumn(const Column &first, const QString &comparison, const Column &second) { toBase().orWhereColumn(first, comparison, second); return *this; } template Builder & Builder::whereColumnEq(const Column &first, const Column &second, const QString &condition) { toBase().whereColumnEq(first, second, condition); return *this; } template Builder & Builder::orWhereColumnEq(const Column &first, const Column &second) { toBase().orWhereColumnEq(first, second); return *this; } template Builder & Builder::whereIn(const Column &column, const QVector &values, const QString &condition, const bool nope) { toBase().whereIn(column, values, condition, nope); return *this; } template Builder & Builder::orWhereIn(const Column &column, const QVector &values) { toBase().orWhereIn(column, values); return *this; } template Builder & Builder::whereNotIn(const Column &column, const QVector &values, const QString &condition) { toBase().whereNotIn(column, values, condition); return *this; } template Builder & Builder::orWhereNotIn(const Column &column, const QVector &values) { toBase().orWhereNotIn(column, values); return *this; } template Builder & Builder::whereNull(const QVector &columns, const QString &condition, bool nope) { toBase().whereNull(columns, condition, nope); return *this; } template Builder &Builder::orWhereNull(const QVector &columns) { toBase().orWhereNull(columns); return *this; } template Builder & Builder::whereNotNull(const QVector &columns, const QString &condition) { toBase().whereNotNull(columns, condition); return *this; } template Builder &Builder::orWhereNotNull(const QVector &columns) { toBase().orWhereNotNull(columns); return *this; } template Builder & Builder::whereNull(const Column &column, const QString &condition, const bool nope) { toBase().whereNull(column, condition, nope); return *this; } template Builder &Builder::orWhereNull(const Column &column) { toBase().orWhereNull(column); return *this; } template Builder & Builder::whereNotNull(const Column &column, const QString &condition) { toBase().whereNotNull(column, condition); return *this; } template Builder &Builder::orWhereNotNull(const Column &column) { toBase().orWhereNotNull(column); return *this; } template template Builder & Builder::where(C &&column, const QString &comparison, V &&value, const QString &condition) { toBase().where(std::forward(column), comparison, std::forward(value), condition); return *this; } template template Builder & Builder::orWhere(C &&column, const QString &comparison, V &&value) { toBase().where(std::forward(column), comparison, std::forward(value), OR); return *this; } template template Builder & Builder::whereEq(C &&column, V &&value, const QString &condition) { toBase().where(std::forward(column), EQ, std::forward(value), condition); return *this; } template template Builder &Builder::orWhereEq(C &&column, V &&value) { toBase().where(std::forward(column), EQ, std::forward(value), OR); return *this; } template template Builder & Builder::whereSub(const Column &column, const QString &comparison, T &&query, const QString &condition) { toBase().whereSub(column, comparison, std::forward(query), condition); return *this; } template Builder & Builder::whereRaw(const QString &sql, const QVector &bindings, const QString &condition) { toBase().whereRaw(sql, bindings, condition); return *this; } template Builder & Builder::orWhereRaw(const QString &sql, const QVector &bindings) { toBase().whereRaw(sql, bindings, OR); return *this; } template Builder &Builder::groupBy(const QVector &groups) { toBase().groupBy(groups); return *this; } template Builder &Builder::groupBy(const Column &group) { toBase().groupBy(group); return *this; } template template Builder &Builder::groupBy(Args &&...groups) { toBase().groupBy(QVector {std::forward(groups)...}); return *this; } template Builder & Builder::groupByRaw(const QString &sql, const QVector &bindings) { toBase().groupByRaw(sql, bindings); return *this; } template Builder & Builder::having(const Column &column, const QString &comparison, const QVariant &value, const QString &condition) { toBase().having(column, comparison, value, condition); return *this; } template Builder & Builder::orHaving(const Column &column, const QString &comparison, const QVariant &value) { toBase().orHaving(column, comparison, value); return *this; } template Builder & Builder::havingRaw(const QString &sql, const QVector &bindings, const QString &condition) { toBase().havingRaw(sql, bindings, condition); return *this; } template Builder & Builder::orHavingRaw(const QString &sql, const QVector &bindings) { toBase().havingRaw(sql, bindings, OR); return *this; } template Builder & Builder::orderBy(const Column &column, const QString &direction) { toBase().orderBy(column, direction); return *this; } template Builder &Builder::orderByDesc(const Column &column) { toBase().orderByDesc(column); return *this; } template Builder &Builder::orderByRaw(const QString &sql, const QVector &bindings) { toBase().orderByRaw(sql, bindings); return *this; } template Builder & Builder::latest(const Column &column) { toBase().latest(getCreatedAtColumnForLatestOldest(column)); return *this; } template Builder & Builder::oldest(const Column &column) { toBase().oldest(getCreatedAtColumnForLatestOldest(column)); return *this; } template Builder &Builder::reorder() { toBase().reorder(); return *this; } template Builder & Builder::reorder(const Column &column, const QString &direction) { toBase().reorder(column, direction); return *this; } template Column Builder::getCreatedAtColumnForLatestOldest(Column column) const { /* Don't initialize column when user passed column expression, only when it holds the QString type. */ if (std::holds_alternative(column) && std::get(column).isEmpty() ) { if (const auto &createdAtColumn = m_model.getCreatedAtColumn(); createdAtColumn.isEmpty() ) column = CREATED_AT; else column = createdAtColumn; } return column; } template Builder & Builder::limit(const int value) { toBase().limit(value); return *this; } template Builder & Builder::take(const int value) { return limit(value); } template Builder & Builder::offset(const int value) { toBase().offset(value); return *this; } template Builder & Builder::skip(const int value) { return offset(value); } template Builder & Builder::forPage(const int page, const int perPage) { toBase().forPage(page, perPage); return *this; } template template requires std::is_arithmetic_v std::tuple Builder::increment(const QString &column, const T amount, const QVector &extra) const { return toBase().template increment(column, amount, addUpdatedAtColumn(extra)); } template template requires std::is_arithmetic_v std::tuple Builder::decrement(const QString &column, const T amount, const QVector &extra) const { return toBase().template decrement(column, amount, addUpdatedAtColumn(extra)); } template Builder &Builder::lockForUpdate() { toBase().lock(true); return *this; } template Builder &Builder::sharedLock() { toBase().lock(false); return *this; } template Builder &Builder::lock(const bool value) { toBase().lock(value); return *this; } template Builder &Builder::lock(const char *value) { toBase().lock(value); return *this; } template Builder &Builder::lock(const QString &value) { toBase().lock(value); return *this; } template Builder &Builder::lock(QString &&value) { toBase().lock(std::move(value)); return *this; } template Model Builder::newModelInstance(const QVector &attributes) { return m_model.newInstance(attributes) .setConnection(m_query->getConnection().getName()); // TODO study, or stackoverflow move or not move? its a question 🤔 silverqx // return std::move(m_model.newInstance(attributes) // .setConnection(m_query->getConnection().getName())); } template QVector Builder::getModels(const QVector &columns) { return hydrate(m_query->get(columns)); } template void Builder::eagerLoadRelations(QVector &models) { if (m_eagerLoad.isEmpty()) return; for (const auto &relation : std::as_const(m_eagerLoad)) /* For nested eager loads we'll skip loading them here and they will be set as an eager load on the query to retrieve the relation so that they will be eager loaded on that query, because that is where they get hydrated as models. */ if (!relation.name.contains(DOT)) /* Get the relation instance for the given relation name, have to be done through the visitor pattern. */ m_model.eagerLoadRelationWithVisitor(relation, *this, models); } template template void Builder::eagerLoadRelationVisited( Relation &&relation, QVector &models, const WithItem &relationItem) const { // TODO docs add similar note for lazy load silverqx /* Look also at EagerRelationStore::visited(), where the whole flow begins. How this relation flow works: EagerRelationStore::visited() obtains a reference by the relation name to the relation method, these relation methods are defined on models as member functions. A reference to the relation methods are defined in the Model::u_relations hash as lambda expressions. These lambda expressions will be visited/invoked by EagerRelationStore::visited() to obtain references to the relation methods. Relation constraints will be disabled for eager relations by Relation::noConstraints() method, these default constraints are only used for lazy loading, for eager constraints are used constraints, which are defined by Relation::addEagerConstraints() virtual methods. To the Relation::noConstraints() method is passed lambda, which invokes obtained reference to the relation method defined on the model and invokes it on the 'new' model instance refered as 'dummyModel'. The Relation instance is created by this relation method, this relation method calls factory method, which creates the Relation instance. Every Relation has it's own Relation::create() factory method, to which the following parameters are passed, newly created Related model instance, current/parent model instance, database column names of the relationship, and for a BelongsTo relation also the name of the relation. The Relation instance saves a non-const reference to the current/parent model instance, a copy of the related model instance because it is created on the stack. The Relation instance creates a new TinyBuilder instance from the Related model instance by TinyBuilder::newQuery() and saves ownership as the unique pointer. Then eager constraints are applied to this newly created TinyBuilder and the result is returned back to the initial model. The result is transformed into models and these models are hydrated. Hydrated models are saved to the Model::m_relations data member. */ /* First we will "back up" the existing where conditions on the query so we can add our eager constraints, this is done in the EagerRelationStore::visited() by help of the Relations::Relation::noConstraints(). Folowing is not implemented for now, it is true for relationItem.constraints: Then we will merge the wheres that were on the query back to it in order that any where conditions might be specified. */ const auto nested = relationsNestedUnder(relationItem.name); /* If there are nested relationships set on the query, we will put those onto the query instances so that they can be handled after this relationship is loaded. In this way they will all trickle down as they are loaded. */ if (nested.size() > 0) relation->getQuery().with(nested); relation->addEagerConstraints(models); // Add relation contraints defined in a user callback // NOTE api different, Eloquent is passing the Relation reference into the lambda, it would be almost impossible to do it silverqx if (relationItem.constraints) std::invoke(relationItem.constraints, relation->getBaseQuery()); /* Once we have the results, we just match those back up to their parent models using the relationship instance. Then we just return the finished vectors of models which have been eagerly hydrated and are readied for return. */ relation->match(relation->initRelation(models, relationItem.name), relation->getEager(), relationItem.name); } template QVector Builder::hydrate(QSqlQuery &&result) { auto instance = newModelInstance(); QVector models; // Table row, instantiate the QVector once and then re-use QVector row; row.reserve(result.record().count()); while (result.next()) { row.clear(); // Populate table row with data from the database const auto record = result.record(); for (int i = 0; i < record.count(); ++i) row.append({record.fieldName(i), result.value(i)}); // Create a new model instance from the table row models.append(instance.newFromBuilder(row)); } return models; } template QVector Builder::parseWithRelations(const QVector &relations) { QVector results; // Can contain nested relations results.reserve(relations.size() * 2); for (auto relation : relations) { const auto isSelectConstraint = relation.name.contains(COLON); if (isSelectConstraint && relation.constraints) throw RuntimeError( "Passing both 'Select constraint' and 'Lambda expression " "constraint' to the Model::with() method is not allowed, use " "only one of them."); if (isSelectConstraint) relation = createSelectWithConstraint(relation.name); /* We need to separate out any nested includes, which allows the developers to load deep relationships using "dots" without stating each level of the relationship with its own key in the vector of eager-load names. */ addNestedWiths(relation.name, results); results.append(std::move(relation)); } return results; } template WithItem Builder::createSelectWithConstraint(const QString &name) { auto splitted = name.split(COLON); auto relation = splitted.at(0).trimmed(); auto &columns = splitted[1]; auto belongsToManyRelatedTable = m_model.getRelatedTableForBelongsToManyWithVisitor(relation); return { std::move(relation), [columns = std::move(columns), belongsToManyRelatedTable = std::move(belongsToManyRelatedTable)] (auto &query) { QVector columnsList; columnsList.reserve(columns.count(COMMA_C) + 1); // Avoid 'clazy might detach' warning for (const auto columns_ = columns.split(COMMA_C); auto column : columns_) { column = column.trimmed(); // Fully qualified column passed, not needed to process if (column.contains(DOT)) { columnsList << std::move(column); continue; } /* Generate fully qualified column name for the BelongsToMany relation. */ if (belongsToManyRelatedTable) { #ifdef __GNUG__ columnsList << QString("%1.%2") .arg(*belongsToManyRelatedTable, column); #else columnsList << QStringLiteral("%1.%2") .arg(*belongsToManyRelatedTable, column); #endif continue; } columnsList << std::move(column); } // TODO move, query.select() silverqx query.select(std::move(columnsList)); } }; } template void Builder::addNestedWiths(const QString &name, QVector &results) const { QStringList progress; /* If the relation has already been set on the result vector, we will not set it again, since that would override any constraints that were already placed on the relationships. We will only set the ones that are not specified. */ // Prevent container detach const auto names = name.split(DOT); progress.reserve(names.size()); for (const auto &segment : names) { progress << segment; auto last = progress.join(DOT); const auto containsRelation = [&last](const auto &relation) { return relation.name == last; }; const auto contains = ranges::contains(results, true, containsRelation); // Don't add a relation in the 'name' variable if (!contains && (last != name)) results.append({std::move(last)}); } } template QVector Builder::relationsNestedUnder(const QString &topRelationName) const { QVector nested; /* We are basically looking for any relationships that are nested deeper than the given top-level relationship. We will just check for any relations that start with the given top relations and adds them to our vectors. */ for (const auto &relation : m_eagerLoad) if (isNestedUnder(topRelationName, relation.name)) nested.append({relation.name.mid(topRelationName.size() + 1), relation.constraints}); return nested; } template bool Builder::isNestedUnder(const QString &topRelation, const QString &nestedRelation) const { return nestedRelation.contains(DOT) && nestedRelation.startsWith(QStringLiteral("%1.").arg(topRelation)); } template QVector Builder::addUpdatedAtColumn(QVector values) const { const auto &updatedAtColumn = m_model.getUpdatedAtColumn(); const auto &qualifiedUpdatedAtColumn = m_model.getQualifiedUpdatedAtColumn(); if (!m_model.usesTimestamps() || updatedAtColumn.isEmpty()) return values; const auto valuesUpdatedAtColumn = std::ranges::find_if(values, [&updatedAtColumn](const auto &updateItem) { return updateItem.column == updatedAtColumn; }); // Not found if (valuesUpdatedAtColumn == std::ranges::cend(values)) values.append({qualifiedUpdatedAtColumn, m_model.freshTimestampString()}); else // Rename updated_at column to the qualified column valuesUpdatedAtColumn->column = qualifiedUpdatedAtColumn; return values; } } // namespace Orm::Tiny #ifdef TINYORM_COMMON_NAMESPACE } // namespace TINYORM_COMMON_NAMESPACE #endif #endif // TINYBUILDER_H