Files
TinyORM/include/orm/query/querybuilder.hpp
2022-05-25 11:58:33 +02:00

1244 lines
48 KiB
C++

#pragma once
#ifndef ORM_QUERY_QUERYBUILDER_HPP
#define ORM_QUERY_QUERYBUILDER_HPP
#include "orm/macros/systemheader.hpp"
TINY_SYSTEM_HEADER
#include <QtSql/QSqlQuery>
#include <unordered_set>
#include "orm/ormconcepts.hpp"
#include "orm/ormtypes.hpp"
#include "orm/query/grammars/grammar.hpp"
TINYORM_BEGIN_COMMON_NAMESPACE
namespace Orm::Query
{
class JoinClause;
/*! Concept for the remove() method parameter. */
template<typename T>
concept Remove = std::convertible_to<T, quint64> ||
std::same_as<T, Query::Expression>;
// TODO add inRandomOrder() silverqx
// TODO QueryBuilder::updateOrInsert() silverqx
/*! Database query builder. */
class SHAREDLIB_EXPORT Builder // clazy:exclude=copyable-polymorphic
{
using QueryGrammar = Query::Grammars::Grammar;
public:
/*! Constructor. */
Builder(DatabaseConnection &connection, const QueryGrammar &grammar);
/* Need to be the polymorphic type because of dynamic_cast<>
in Grammar::concatenateWhereClauses(). */
/*! Virtual destructor. */
inline virtual ~Builder() = default;
/*! Copy constructor. */
inline Builder(const Builder &) = default;
/*! Deleted copy assignment operator (class constains reference and const). */
Builder &operator=(const Builder &) = delete;
/*! Move constructor. */
inline Builder(Builder &&) = default;
/*! Deleted move assignment operator (class constains reference and const). */
Builder &operator=(Builder &&) = delete;
/* Retrieving results */
/*! Execute the query as a "select" statement. */
QSqlQuery get(const QVector<Column> &columns = {ASTERISK});
/*! Execute a query for a single record by ID. */
QSqlQuery find(const QVariant &id, const QVector<Column> &columns = {ASTERISK});
/*! Execute the query and get the first result. */
QSqlQuery first(const QVector<Column> &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<QVariant> pluck(const QString &column);
/*! Get the vector with the values of a given column. */
template<typename T>
std::map<T, QVariant> pluck(const QString &column, const QString &key);
/*! Get the SQL representation of the query. */
QString toSql();
// TODO next implement dd silverqx
/*! Die and dump the current SQL and bindings. */
// void dd() const
// { dd($this->toSql(), $this->getBindings()); }
/* Insert, Update, Delete */
/*! Insert new records into the database. */
std::optional<QSqlQuery>
insert(const QVector<QVariantMap> &values);
/*! Insert a new record into the database. */
std::optional<QSqlQuery>
insert(const QVariantMap &values);
/*! Insert new records into the database (multi insert). */
std::optional<QSqlQuery>
insert(const QVector<QString> &columns, const QVector<QVector<QVariant>> &values);
/*! Insert a new record and get the value of the primary key. */
quint64 insertGetId(const QVariantMap &values, const QString &sequence = "");
/*! Insert new records into the database while ignoring errors. */
std::tuple<int, std::optional<QSqlQuery>>
insertOrIgnore(const QVector<QVariantMap> &values);
/*! Insert a new record into the database while ignoring errors. */
std::tuple<int, std::optional<QSqlQuery>>
insertOrIgnore(const QVariantMap &values);
/*! Insert new records into the database while ignoring errors (multi insert). */
std::tuple<int, std::optional<QSqlQuery>>
insertOrIgnore(const QVector<QString> &columns,
const QVector<QVector<QVariant>> &values);
/*! Update records in the database. */
std::tuple<int, QSqlQuery>
update(const QVector<UpdateItem> &values);
/*! Delete records from the database. */
std::tuple<int, QSqlQuery> deleteRow();
/*! Delete records from the database. */
std::tuple<int, QSqlQuery> remove();
/*! Delete records from the database. */
template<Remove T>
inline std::tuple<int, QSqlQuery> deleteRow(T &&id);
/*! Delete records from the database. */
template<Remove T>
std::tuple<int, QSqlQuery> remove(T &&id);
/*! Run a truncate statement on the table. */
void truncate();
/* Select */
/*! Retrieve the "count" result of the query. */
inline quint64 count(const QVector<Column> &columns = {ASTERISK}) const;
/*! Retrieve the "count" result of the query. */
template<typename = void>
inline quint64 count(const Column &column);
/*! Retrieve the minimum value of a given column. */
inline QVariant min(const Column &column) const;
/*! Retrieve the maximum value of a given column. */
inline QVariant max(const Column &column) const;
/*! Retrieve the sum of the values of a given column. */
inline QVariant sum(const Column &column) const;
/*! Retrieve the average of the values of a given column. */
inline QVariant avg(const Column &column) const;
/*! Alias for the "avg" method. */
inline QVariant average(const Column &column) const;
/*! Execute an aggregate function on the database. */
QVariant aggregate(const QString &function,
const QVector<Column> &columns = {ASTERISK}) const;
/*! Set the columns to be selected. */
Builder &select(const QVector<Column> &columns = {ASTERISK});
/*! Set the column to be selected. */
Builder &select(const Column &column);
/*! Add new select columns to the query. */
Builder &addSelect(const QVector<Column> &columns);
/*! Add a new select column to the query. */
Builder &addSelect(const Column &column);
/*! Set a select subquery on the query. */
template<Queryable T>
inline Builder &select(T &&query, const QString &as);
/*! Add a select subquery to the query. */
template<Queryable T>
Builder &addSelect(T &&query, const QString &as);
/*! Add a subselect expression to the query. */
template<SubQuery T>
Builder &selectSub(T &&query, const QString &as);
/*! Add a new "raw" select expression to the query. */
Builder &selectRaw(const QString &expression,
const QVector<QVariant> &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);
/*! Set the table which the query is targeting. */
Builder &from(const QString &table, const QString &as = "");
/*! Set the table which the query is targeting. */
Builder &from(const Expression &table);
/*! Set the table which the query is targeting. */
Builder &from(Expression &&table);
/*! Makes "from" fetch from a subquery. */
template<SubQuery T>
Builder &fromSub(T &&query, const QString &as);
/*! Set the table which the query is targeting. */
Builder &fromRaw(const QString &expression,
const QVector<QVariant> &bindings = {});
/*! Add a join clause to the query. */
template<JoinTable T>
inline Builder &
join(T &&table, const QString &first, const QString &comparison,
const QVariant &second, const QString &type = INNER, bool where = false);
/*! Add an advanced join clause to the query. */
template<JoinTable T>
inline Builder &
join(T &&table, const std::function<void(JoinClause &)> &callback,
const QString &type = INNER);
/*! Add a "join where" clause to the query. */
template<JoinTable T>
inline 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<JoinTable T>
inline Builder &
leftJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second);
/*! Add an advanced left join to the query. */
template<JoinTable T>
inline Builder &
leftJoin(T &&table, const std::function<void(JoinClause &)> &callback);
/*! Add a "join where" clause to the query. */
template<JoinTable T>
inline Builder &
leftJoinWhere(T &&table, const QString &first, const QString &comparison,
const QVariant &second);
/*! Add a right join to the query. */
template<JoinTable T>
inline Builder &
rightJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second);
/*! Add an advanced right join to the query. */
template<JoinTable T>
inline Builder &
rightJoin(T &&table, const std::function<void(JoinClause &)> &callback);
/*! Add a "right join where" clause to the query. */
template<JoinTable T>
inline Builder &
rightJoinWhere(T &&table, const QString &first, const QString &comparison,
const QVariant &second);
/*! Add a "cross join" clause to the query. */
template<JoinTable T>
inline Builder &
crossJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second);
/*! Add an advanced "cross join" clause to the query. */
template<JoinTable T>
inline Builder &
crossJoin(T &&table, const std::function<void(JoinClause &)> &callback);
/*! Add a subquery join clause to the query. */
template<SubQuery T>
inline 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<SubQuery T>
inline Builder &
joinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback,
const QString &type = INNER);
/*! Add a subquery left join to the query. */
template<SubQuery T>
inline 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<SubQuery T>
inline Builder &
leftJoinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback);
/*! Add a subquery right join to the query. */
template<SubQuery T>
inline 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<SubQuery T>
inline Builder &
rightJoinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback);
/*! Add a basic where clause to the query. */
template<WhereValue T>
Builder &where(const Column &column, const QString &comparison,
T &&value, const QString &condition = AND);
/*! Add an "or where" clause to the query. */
template<WhereValue T>
Builder &orWhere(const Column &column, const QString &comparison, T &&value);
/*! Add a basic equal where clause to the query. */
template<WhereValue T>
Builder &whereEq(const Column &column, T &&value,
const QString &condition = AND);
/*! Add an equal "or where" clause to the query. */
template<WhereValue T>
Builder &orWhereEq(const Column &column, T &&value);
/*! Add a nested where clause to the query. */
Builder &where(const std::function<void(Builder &)> &callback,
const QString &condition = AND);
/*! Add a nested "or where" clause to the query. */
Builder &orWhere(const std::function<void(Builder &)> &callback);
/*! Add a vector of basic where clauses to the query. */
Builder &where(const QVector<WhereItem> &values,
const QString &condition = AND);
/*! Add a vector of basic "or where" clauses to the query. */
Builder &orWhere(const QVector<WhereItem> &values);
/*! Add a vector of where clauses comparing two columns to the query. */
Builder &whereColumn(const QVector<WhereColumnItem> &values,
const QString &condition = AND);
/*! Add a vector of "or where" clauses comparing two columns to the query. */
Builder &orWhereColumn(const QVector<WhereColumnItem> &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<QVariant> &values,
const QString &condition = AND, bool nope = false);
/*! Add an "or where in" clause to the query. */
Builder &orWhereIn(const Column &column, const QVector<QVariant> &values);
/*! Add a "where not in" clause to the query. */
Builder &whereNotIn(const Column &column, const QVector<QVariant> &values,
const QString &condition = AND);
/*! Add an "or where not in" clause to the query. */
Builder &orWhereNotIn(const Column &column, const QVector<QVariant> &values);
/*! Add a "where null" clause to the query. */
Builder &whereNull(const QVector<Column> &columns = {ASTERISK},
const QString &condition = AND, bool nope = false);
/*! Add an "or where null" clause to the query. */
Builder &orWhereNull(const QVector<Column> &columns = {ASTERISK});
/*! Add a "where not null" clause to the query. */
Builder &whereNotNull(const QVector<Column> &columns = {ASTERISK},
const QString &condition = AND);
/*! Add an "or where not null" clause to the query. */
Builder &orWhereNotNull(const QVector<Column> &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<Queryable C, WhereValue V>
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<Queryable C, WhereValue V>
inline 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<Queryable C, WhereValue V>
inline 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<Queryable C, WhereValue V>
inline Builder &orWhereEq(C &&column, V &&value);
/*! Add a full sub-select to the "where" clause. */
template<WhereValueSubQuery T>
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<QVariant> &bindings = {},
const QString &condition = AND);
/*! Add a raw "or where" clause to the query. */
Builder &orWhereRaw(const QString &sql, const QVector<QVariant> &bindings = {});
/*! Add a "group by" clause to the query. */
Builder &groupBy(const QVector<Column> &groups);
/*! Add a "group by" clause to the query. */
Builder &groupBy(const Column &group);
/*! Add a "group by" clause to the query. */
template<ColumnConcept ...Args>
inline Builder &groupBy(Args &&...groups);
/*! Add a raw "groupBy" clause to the query. */
Builder &groupByRaw(const QString &sql, const QVector<QVariant> &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<QVariant> &bindings = {},
const QString &condition = AND);
/*! Add a raw "or having" clause to the query. */
Builder &orHavingRaw(const QString &sql, const QVector<QVariant> &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 an "order by" clause to the query with a subquery ordering. */
template<Queryable T>
Builder &orderBy(T &&query, const QString &direction = ASC);
/*! Add a descending "order by" clause to the query with a subquery ordering. */
template<Queryable T>
inline Builder &orderByDesc(T &&query);
/*! Add a raw "order by" clause to the query. */
Builder &orderByRaw(const QString &sql, const QVector<QVariant> &bindings = {});
/*! Add an "order by" clause for a timestamp to the query. */
Builder &latest(const Column &column = CREATED_AT);
/*! Add an "order by" clause for a timestamp to the query. */
Builder &oldest(const Column &column = CREATED_AT);
/*! 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<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
increment(const QString &column, T amount = 1,
const QVector<UpdateItem> &extra = {});
/*! Decrement a column's value by a given amount. */
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
decrement(const QString &column, T amount = 1,
const QVector<UpdateItem> &extra = {});
/* 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);
/* Getters / Setters */
/*! Get a database connection. */
inline DatabaseConnection &getConnection() const;
/*! Get the query grammar instance. */
inline const QueryGrammar &getGrammar() const;
/*! Get the current query value bindings as flattened QVector. */
QVector<QVariant> getBindings() const;
/*! Get the raw map of bindings. */
inline const BindingsMap &getRawBindings() const;
/*! Add a binding to the query. */
Builder &addBinding(const QVariant &binding,
BindingType type = BindingType::WHERE);
/*! Add bindings to the query. */
Builder &addBinding(const QVector<QVariant> &bindings,
BindingType type = BindingType::WHERE);
/*! Add bindings to the query. */
Builder &addBinding(QVector<QVariant> &&bindings,
BindingType type = BindingType::WHERE);
/*! Set the bindings on the query builder. */
Builder &setBindings(QVector<QVariant> &&bindings,
BindingType type = BindingType::WHERE);
/*! Get an aggregate function and column to be run. */
inline const std::optional<AggregateItem> &getAggregate() const;
/*! Check if the query returns distinct results. */
inline const std::variant<bool, QStringList> &getDistinct() const;
/*! Check if the query returns distinct results. */
template<typename T> requires std::same_as<T, bool>
inline bool getDistinct() const;
/*! Check if the query returns distinct results. */
template<typename T> requires std::same_as<T, QStringList>
inline const QStringList &getDistinct() const;
// TODO check up all code and return references when appropriate silverqx
/*! Get the columns that should be returned. */
inline const QVector<Column> &getColumns() const;
/*! Set the columns that should be returned. */
inline Builder &setColumns(const QVector<Column> &columns);
/*! Get the table associated with the query builder. */
inline const FromClause &getFrom() const;
/*! Get the table joins for the query. */
inline const QVector<QSharedPointer<JoinClause>> &getJoins() const;
/*! Get the where constraints for the query. */
inline const QVector<WhereConditionItem> &getWheres() const;
/*! Get the groupings for the query. */
inline const QVector<Column> &getGroups() const;
/*! Get the having constraints for the query. */
inline const QVector<HavingConditionItem> &getHavings() const;
/*! Get the orderings for the query. */
inline const QVector<OrderByItem> &getOrders() const;
/*! Get the maximum number of records to return. */
inline int getLimit() const;
/*! Get the number of records to skip. */
inline int getOffset() const;
/*! Get the row locking. */
inline const std::variant<std::monostate, bool, QString> &getLock() const;
/* Other methods */
/*! Get a new instance of the query builder. */
virtual QSharedPointer<Builder> newQuery() const;
/*! Create a new query instance for nested where condition. */
QSharedPointer<Builder> forNestedWhere() const;
/*! Create a raw database expression. */
Expression raw(const QVariant &value) const;
/*! Add another query builder as a nested where to the query builder. */
Builder &addNestedWhereQuery(const QSharedPointer<Builder> &query,
const QString &condition);
/*! Add an "exists" clause to the query. */
Builder &addWhereExistsQuery(const QSharedPointer<Builder> &query,
const QString &condition = AND, bool nope = false);
/*! Merge an array of where clauses and bindings. */
Builder &mergeWheres(const QVector<WhereConditionItem> &wheres,
const QVector<QVariant> &bindings);
/*! Merge an array of where clauses and bindings. */
Builder &mergeWheres(QVector<WhereConditionItem> &&wheres,
QVector<QVariant> &&bindings);
/*! Builder property types. */
enum struct PropertyType
{
COLUMNS,
};
/*! Clone the query without the given properties. */
Builder cloneWithout(const std::unordered_set<PropertyType> &properties) const;
/*! Clone the query without the given bindings. */
Builder cloneWithoutBindings(
const std::unordered_set<BindingType> &except) const;
protected:
/*! Determine if the given operator is supported. */
bool invalidOperator(const QString &comparison) const;
/*! Remove all of the expressions from a list of bindings. */
QVector<QVariant> cleanBindings(const QVector<QVariant> &bindings) const;
/*! Add a vector of basic where clauses to the query. */
Builder &
addArrayOfWheres(const QVector<WhereItem> &values,
const QString &condition = AND);
/*! Add a vector of where clauses comparing two columns to the query. */
Builder &
addArrayOfWheres(const QVector<WhereColumnItem> &values,
const QString &condition = AND);
/*! Get a new join clause. */
QSharedPointer<JoinClause>
newJoinClause(const Builder &query, const QString &type,
const QString &table) const;
/*! Get a new join clause. */
QSharedPointer<JoinClause>
newJoinClause(const Builder &query, const QString &type,
Expression &&table) const;
/*! Remove all existing columns and column bindings. */
Builder &clearColumns();
/*! Execute the given callback while selecting the given columns. */
QSqlQuery
onceWithColumns(const QVector<Column> &columns,
const std::function<QSqlQuery()> &callback);
/*! Creates a subquery and parse it. */
std::pair<QString, QVector<QVariant>>
createSub(const std::function<void(Builder &)> &callback) const;
/*! Creates a subquery and parse it. */
std::pair<QString, QVector<QVariant>>
createSub(Builder &query) const;
/*! Creates a subquery and parse it. */
std::pair<QString, QVector<QVariant>>
createSub(const QString &query) const;
/*! Creates a subquery and parse it. */
std::pair<QString, QVector<QVariant>>
createSub(QString &&query) const;
/*! Determine whether the T type is a query builder instance or a lambda expr. */
template<typename T>
constexpr static bool isQueryable =
std::is_convertible_v<T, Orm::QueryBuilder &> ||
std::is_invocable_v<T, Orm::QueryBuilder &>;
/*! Create a new query instance for a sub-query. */
inline virtual QSharedPointer<Builder> forSubQuery() const;
/*! 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;
/* Getters / Setters */
/*! Set the aggregate property without running the query. */
Builder &setAggregate(const QString &function,
const QVector<Column> &columns = {ASTERISK});
private:
/*! Run the query as a "select" statement against the connection. */
QSqlQuery runSelect();
/*! Set the table which the query is targeting. */
inline Builder &setFrom(const FromClause &from);
/*! Add a join clause to the query, common code. */
Builder &joinInternal(
QSharedPointer<JoinClause> &&join, const QString &first,
const QString &comparison, const QVariant &second, bool where);
/*! Add an advanced join clause to the query, common code. */
Builder &joinInternal(
QSharedPointer<JoinClause> &&join,
const std::function<void(JoinClause &)> &callback);
/*! Add a join clause to the query, common code for the above two methods. */
Builder &joinInternal(QSharedPointer<JoinClause> &&join);
/*! Add a subquery join clause to the query, common code. */
Builder &joinSubInternal(
std::pair<QString, QVector<QVariant>> &&subQuery, const QString &as,
const QString &first, const QString &comparison, const QVariant &second,
const QString &type, bool where);
/*! Add a subquery join clause to the query, common code. */
Builder &joinSubInternal(
std::pair<QString, QVector<QVariant>> &&subQuery, const QString &as,
const std::function<void(JoinClause &)> &callback,
const QString &type);
/*! Add a basic where clause to the query, common code. */
Builder &whereInternal(const Column &column, const QString &comparison,
const QVariant &value, const QString &condition);
/*! Throw exception when m_bindings doesn't contain a passed type. */
void checkBindingType(BindingType type) const;
/*! All of the available clause operators. */
static const QVector<QString> &getOperators();
/*! The database connection instance. */
DatabaseConnection &m_connection;
/*! The database query grammar instance. */
const QueryGrammar &m_grammar;
/*! The current query value bindings.
Order is crucial here because of that QMap with an enum struct is used. */
BindingsMap m_bindings {
{BindingType::SELECT, {}},
{BindingType::FROM, {}},
{BindingType::JOIN, {}},
{BindingType::WHERE, {}},
{BindingType::GROUPBY, {}},
{BindingType::HAVING, {}},
{BindingType::ORDER, {}},
{BindingType::UNION, {}},
{BindingType::UNIONORDER, {}},
};
/*! An aggregate function and column to be run. */
std::optional<AggregateItem> m_aggregate = std::nullopt;
/*! Indicates if the query returns distinct results. */
std::variant<bool, QStringList> m_distinct = false;
/*! The columns that should be returned. */
QVector<Column> m_columns {};
/*! The table which the query is targeting. */
FromClause m_from {};
/*! The table joins for the query. */
QVector<QSharedPointer<JoinClause>> m_joins {};
/*! The where constraints for the query. */
QVector<WhereConditionItem> m_wheres {};
/*! The groupings for the query. */
QVector<Column> m_groups {};
/*! The having constraints for the query. */
QVector<HavingConditionItem> m_havings {};
/*! The orderings for the query. */
QVector<OrderByItem> m_orders {};
/*! The maximum number of records to return. */
int m_limit = -1;
/*! The number of records to skip. */
int m_offset = -1;
/*! Indicates whether row locking is being used. */
std::variant<std::monostate, bool, QString> m_lock {};
};
/* public */
/* Retrieving results */
template<typename T>
std::map<T, QVariant>
Builder::pluck(const QString &column, const QString &key)
{
/* First, we will need to select the results of the query accounting for the
given column / key. Once we have the results, we will be able to take
the results and get the exact data that was requested for the query. */
auto query = get({column, key});
const auto size = query.size();
// Empty result
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. */
const auto unqualifiedColumn = stripTableForPluck(column);
const auto unqualifiedKey = stripTableForPluck(key);
std::map<T, QVariant> result;
while (query.next())
result.emplace(std::make_pair(query.value(unqualifiedKey).value<T>(),
query.value(unqualifiedColumn)));
return result;
}
/* Insert, Update, Delete */
template<Remove T>
std::tuple<int, QSqlQuery> Builder::deleteRow(T &&id)
{
return remove(std::forward<T>(id));
}
template<Remove T>
std::tuple<int, QSqlQuery> Builder::remove(T &&id)
{
/* If an ID is passed to the method, we will set the where clause to check the
ID to let developers to simply and quickly remove a single row from this
database without manually specifying the "where" clauses on the query.
m_from will be wrapped in the Grammar. */
where(QStringLiteral("%1.id").arg(std::get<QString>(m_from)), EQ,
std::forward<T>(id), AND);
return remove();
}
/* Select */
quint64 Builder::count(const QVector<Column> &columns) const
{
return aggregate(QStringLiteral("count"), columns).template value<quint64>();
}
template<typename>
quint64 Builder::count(const Column &column)
{
return aggregate(QStringLiteral("count"), {column}).template value<quint64>();
}
QVariant Builder::min(const Column &column) const
{
return aggregate(QStringLiteral("min"), {column});
}
QVariant Builder::max(const Column &column) const
{
return aggregate(QStringLiteral("max"), {column});
}
QVariant Builder::sum(const Column &column) const
{
auto result = aggregate(QStringLiteral("sum"), {column});
if (!result.isValid() || result.isNull())
result = 0;
return result;
}
QVariant Builder::avg(const Column &column) const
{
return aggregate(QStringLiteral("avg"), {column});
}
QVariant Builder::average(const Column &column) const
{
return avg(column);
}
template<Queryable T>
Builder &Builder::select(T &&query, const QString &as)
{
return selectSub(std::forward<T>(query), as);
}
template<Queryable T>
Builder &Builder::addSelect(T &&query, const QString &as)
{
if (m_columns.isEmpty())
select(QVector<Column> {QStringLiteral("%1.*")
.arg(std::get<QString>(m_from))});
return selectSub(std::forward<T>(query), as);
}
// TODO docs silverqx
template<SubQuery T>
Builder &Builder::selectSub(T &&query, const QString &as)
{
auto [queryString, bindings] = createSub(std::forward<T>(query));
return selectRaw(QStringLiteral("(%1) as %2").arg(queryString,
m_grammar.wrap(as)),
bindings);
}
template<SubQuery T>
Builder &
Builder::fromSub(T &&query, const QString &as)
{
auto [queryString, bindings] = createSub(std::forward<T>(query));
return fromRaw(QStringLiteral("(%1) as %2").arg(queryString,
m_grammar.wrapTable(as)),
bindings);
}
template<JoinTable T>
Builder &
Builder::join(T &&table, const QString &first, const QString &comparison,
const QVariant &second, const QString &type, const bool where)
{
// Ownership of the QSharedPointer<JoinClause>
return joinInternal(newJoinClause(*this, type, std::forward<T>(table)),
first, comparison, second, where);
}
// FUTURE joinSub, missing where param, also in joinSub silverqx
template<JoinTable T>
Builder &
Builder::join(T &&table, const std::function<void(JoinClause &)> &callback,
const QString &type)
{
// Ownership of the QSharedPointer<JoinClause>
return joinInternal(newJoinClause(*this, type, std::forward<T>(table)),
callback);
}
template<JoinTable T>
Builder &
Builder::joinWhere(T &&table, const QString &first, const QString &comparison,
const QVariant &second, const QString &type)
{
return join(std::forward<T>(table), first, comparison, second, type, true);
}
template<JoinTable T>
Builder &
Builder::leftJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second)
{
return join(std::forward<T>(table), first, comparison, second, LEFT);
}
template<JoinTable T>
Builder &
Builder::leftJoin(T &&table, const std::function<void(JoinClause &)> &callback)
{
return join(std::forward<T>(table), callback, LEFT);
}
template<JoinTable T>
Builder &
Builder::leftJoinWhere(T &&table, const QString &first, const QString &comparison,
const QVariant &second)
{
return joinWhere(std::forward<T>(table), first, comparison, second, LEFT);
}
template<JoinTable T>
Builder &
Builder::rightJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second)
{
return join(table, first, comparison, second, RIGHT);
}
template<JoinTable T>
Builder &
Builder::rightJoin(T &&table, const std::function<void(JoinClause &)> &callback)
{
return join(table, callback, RIGHT);
}
template<JoinTable T>
Builder &
Builder::rightJoinWhere(T &&table, const QString &first, const QString &comparison,
const QVariant &second)
{
return joinWhere(table, first, comparison, second, RIGHT);
}
// TODO docs missing example, because of different api silverqx
// NOTE api different silverqx
template<JoinTable T>
Builder &
Builder::crossJoin(T &&table, const QString &first, const QString &comparison,
const QVariant &second)
{
return join(table, first, comparison, second, CROSS);
}
template<JoinTable T>
Builder &
Builder::crossJoin(T &&table, const std::function<void(JoinClause &)> &callback)
{
return join(table, callback, CROSS);
}
template<SubQuery T>
Builder &
Builder::joinSub(T &&query, const QString &as, const QString &first,
const QString &comparison, const QVariant &second,
const QString &type, const bool where)
{
return joinSubInternal(createSub(std::forward<T>(query)),
as, first, comparison, second, type, where);
}
template<SubQuery T>
Builder &
Builder::joinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback,
const QString &type)
{
return joinSubInternal(createSub(std::forward<T>(query)), as, callback, type);
}
template<SubQuery T>
Builder &
Builder::leftJoinSub(T &&query, const QString &as, const QString &first,
const QString &comparison, const QVariant &second)
{
return joinSub(std::forward<T>(query), as, first, comparison, second, LEFT);
}
template<SubQuery T>
Builder &
Builder::leftJoinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback)
{
return joinSub(std::forward<T>(query), as, callback, LEFT);
}
template<SubQuery T>
Builder &
Builder::rightJoinSub(T &&query, const QString &as, const QString &first,
const QString &comparison, const QVariant &second)
{
return joinSub(std::forward<T>(query), as, first, comparison, second, RIGHT);
}
template<SubQuery T>
Builder &
Builder::rightJoinSub(T &&query, const QString &as,
const std::function<void(JoinClause &)> &callback)
{
return joinSub(std::forward<T>(query), as, callback, RIGHT);
}
template<WhereValue T>
Builder &
Builder::where(const Column &column, const QString &comparison, T &&value,
const QString &condition)
{
/* If the value is queryable, it means the developer is performing an entire
sub-select within the query and we will need to compile the sub-select
within the where clause to get the appropriate query record results. */
if constexpr (isQueryable<T>)
return whereSub(column, comparison, std::forward<T>(value), condition);
else
return whereInternal(column, comparison, std::forward<T>(value), condition);
}
template<WhereValue T>
Builder &
Builder::orWhere(const Column &column, const QString &comparison, T &&value)
{
return where(column, comparison, std::forward<T>(value), OR);
}
template<WhereValue T>
Builder &
Builder::whereEq(const Column &column, T &&value, const QString &condition)
{
return where(column, EQ, std::forward<T>(value), condition);
}
template<WhereValue T>
Builder &Builder::orWhereEq(const Column &column, T &&value)
{
return where(column, EQ, std::forward<T>(value), OR);
}
template<Queryable C, WhereValue V>
Builder &
Builder::where(C &&column, const QString &comparison, V &&value,
const QString &condition)
{
/* If the column is a Closure instance and there is an operator value, we will
assume the developer wants to run a subquery and then compare the result
of that subquery with the given value that was provided to the method. */
// comparison operator check will be done in the where() method
auto [queryString, bindings] = createSub(std::forward<C>(column));
addBinding(std::move(bindings), BindingType::WHERE);
return where(Expression(PARENTH_ONE.arg(queryString)), comparison,
std::forward<V>(value), condition);
}
template<Queryable C, WhereValue V>
Builder &Builder::orWhere(C &&column, const QString &comparison, V &&value)
{
return where(std::forward<C>(column), comparison, std::forward<V>(value), OR);
}
template<Queryable C, WhereValue V>
Builder &Builder::whereEq(C &&column, V &&value, const QString &condition)
{
return where(std::forward<C>(column), EQ, std::forward<V>(value), condition);
}
template<Queryable C, WhereValue V>
Builder &Builder::orWhereEq(C &&column, V &&value)
{
return where(std::forward<C>(column), EQ, std::forward<V>(value), OR);
}
template<WhereValueSubQuery T>
Builder &
Builder::whereSub(const Column &column, const QString &comparison,
T &&query, const QString &condition)
{
// comparison operator check will be done in the where() method
auto [queryString, bindings] = createSub(std::forward<T>(query));
addBinding(std::move(bindings), BindingType::WHERE);
return where(column, comparison, Expression(PARENTH_ONE.arg(queryString)),
condition);
}
template<ColumnConcept ...Args>
Builder &Builder::groupBy(Args &&...groups)
{
return groupBy(QVector<Column> {std::forward<Args>(groups)...});
}
template<Queryable T>
Builder &Builder::orderBy(T &&query, const QString &direction)
{
auto [queryString, bindings] = createSub(std::forward<T>(query));
addBinding(std::move(bindings), BindingType::ORDER);
return orderBy(Expression(PARENTH_ONE.arg(queryString)), direction);
}
template<Queryable T>
Builder &Builder::orderByDesc(T &&query)
{
return orderBy(std::forward<T>(query), DESC);
}
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
Builder::increment(const QString &column, const T amount,
const QVector<UpdateItem> &extra)
{
const auto expression = QStringLiteral("%1 + %2").arg(m_grammar.wrap(column))
.arg(amount);
QVector<UpdateItem> columns {{column, raw(expression)}};
std::copy(extra.cbegin(), extra.cend(), std::back_inserter(columns));
return update(columns);
}
template<typename T> requires std::is_arithmetic_v<T>
std::tuple<int, QSqlQuery>
Builder::decrement(const QString &column, const T amount,
const QVector<UpdateItem> &extra)
{
const auto expression = QStringLiteral("%1 - %2").arg(m_grammar.wrap(column))
.arg(amount);
QVector<UpdateItem> columns {{column, raw(expression)}};
std::copy(extra.cbegin(), extra.cend(), std::back_inserter(columns));
return update(columns);
}
/* Getters / Setters */
DatabaseConnection &Builder::getConnection() const
{
return m_connection;
}
const Builder::QueryGrammar &Builder::getGrammar() const
{
return m_grammar;
}
const BindingsMap &Builder::getRawBindings() const
{
return m_bindings;
}
const std::optional<AggregateItem> &Builder::getAggregate() const
{
return m_aggregate;
}
const std::variant<bool, QStringList> &
Builder::getDistinct() const
{
return m_distinct;
}
template<typename T> requires std::same_as<T, bool>
bool Builder::getDistinct() const
{
return std::get<bool>(m_distinct);
}
template<typename T> requires std::same_as<T, QStringList>
const QStringList &
Builder::getDistinct() const
{
return std::get<QStringList>(m_distinct);
}
const QVector<Column> &
Builder::getColumns() const
{
return m_columns;
}
Builder &
Builder::setColumns(const QVector<Column> &columns)
{
m_columns = columns;
return *this;
}
const FromClause &
Builder::getFrom() const
{
return m_from;
}
const QVector<QSharedPointer<JoinClause>> &
Builder::getJoins() const
{
return m_joins;
}
const QVector<WhereConditionItem> &
Builder::getWheres() const
{
return m_wheres;
}
const QVector<Column> &
Builder::getGroups() const
{
return m_groups;
}
const QVector<HavingConditionItem> &
Builder::getHavings() const
{
return m_havings;
}
const QVector<OrderByItem> &
Builder::getOrders() const
{
return m_orders;
}
int Builder::getLimit() const
{
return m_limit;
}
int Builder::getOffset() const
{
return m_offset;
}
const std::variant<std::monostate, bool, QString> &
Builder::getLock() const
{
return m_lock;
}
/* protected */
QSharedPointer<Builder>
Builder::forSubQuery() const
{
return newQuery();
}
/* private */
Builder &
Builder::setFrom(const FromClause &from)
{
m_from = from;
return *this;
}
} // namespace Orm::Query
TINYORM_END_COMMON_NAMESPACE
#endif // ORM_QUERY_QUERYBUILDER_HPP