From 867a5f6587aa5dde4cdc2a5ff110bd4fbcd64af3 Mon Sep 17 00:00:00 2001 From: silverqx Date: Tue, 6 Jul 2021 18:28:58 +0200 Subject: [PATCH] added missing raw methods or/whereRaw(), groupByRaw(), or/havingRaw(), orderByRaw(). - added docs - tested manually in Playground --- docs/query-builder.md | 49 +++++++++++ include/orm/ormtypes.hpp | 10 ++- include/orm/query/grammars/grammar.hpp | 3 + include/orm/query/querybuilder.hpp | 25 +++++- include/orm/tiny/model.hpp | 98 ++++++++++++++++++++++ include/orm/tiny/relations/relation.hpp | 86 +++++++++++++++++++ include/orm/tiny/tinybuilder.hpp | 68 +++++++++++++++ src/orm/query/grammars/grammar.cpp | 21 ++++- src/orm/query/grammars/mysqlgrammar.cpp | 1 + src/orm/query/grammars/postgresgrammar.cpp | 1 + src/orm/query/grammars/sqlitegrammar.cpp | 1 + src/orm/query/querybuilder.cpp | 48 +++++++++++ 12 files changed, 403 insertions(+), 8 deletions(-) diff --git a/docs/query-builder.md b/docs/query-builder.md index 3db7f8666..845e9787a 100644 --- a/docs/query-builder.md +++ b/docs/query-builder.md @@ -148,6 +148,55 @@ The `selectRaw` method can be used in place of `addSelect(DB::raw(...))`. This m ->selectRaw("price * ? as price_with_tax", {1.0825}) .get(); + +#### `fromRaw` + +The `fromRaw` method may be used to provide a raw string as the value of the "from" clause: + + auto users = DB::connection("postgres").query() + ->fromRaw("(select id, name from users where id < ?) as u", {5}) + .where("id", "<", 3) + .get(); + + +#### `whereRaw / orWhereRaw` + +The `whereRaw` and `orWhereRaw` methods can be used to inject a raw "where" clause into your query. These methods accept an optional vector of bindings as their second argument: + + auto orders = DB::table("orders") + ->whereRaw("price > IF(state = \"TX\", ?, 100)", {200}) + .get(); + + +### `groupByRaw` + +The `groupByRaw` method may be used to provide a raw string as the value of the `group by` clause: + + auto orders = DB::table("orders") + ->select({"city", "state"}) + .groupByRaw("city, state") + .get(); + + +#### `havingRaw / orHavingRaw` + +The `havingRaw` and `orHavingRaw` methods may be used to provide a raw string as the value of the "having" clause. These methods accept an optional vector of bindings as their second argument: + + auto orders = DB::table("orders") + ->select({"department", DB::raw("SUM(price) as total_sales")}) + .groupBy("department") + .havingRaw("SUM(price) > ?", {2500}) + .get(); + + +#### `orderByRaw` + +The `orderByRaw` method may be used to provide a raw string as the value of the "order by" clause: + + auto orders = DB::table("orders") + ->orderByRaw("updated_at - created_at DESC") + .get(); + ## Joins diff --git a/include/orm/ormtypes.hpp b/include/orm/ormtypes.hpp index 7549764bc..528fb98c1 100644 --- a/include/orm/ormtypes.hpp +++ b/include/orm/ormtypes.hpp @@ -69,6 +69,7 @@ namespace Query NOT_IN, NULL_, NOT_NULL, + RAW, }; struct WhereConditionItem @@ -81,13 +82,16 @@ namespace Query QSharedPointer nestedQuery {nullptr}; QVector values {}; Column columnTwo {}; + QString sql {}; }; enum struct HavingType { UNDEFINED = -1, BASIC, + RAW, }; + struct HavingConditionItem { Column column; @@ -95,12 +99,14 @@ namespace Query QString comparison {"="}; QString condition {"and"}; HavingType type {HavingType::UNDEFINED}; + QString sql {}; }; struct OrderByItem { - Column column; - QString direction {"asc"}; + Column column; + QString direction {"asc"}; + QString sql {}; }; struct SHAREDLIB_EXPORT UpdateItem diff --git a/include/orm/query/grammars/grammar.hpp b/include/orm/query/grammars/grammar.hpp index 92d905b1e..64e7e3367 100644 --- a/include/orm/query/grammars/grammar.hpp +++ b/include/orm/query/grammars/grammar.hpp @@ -140,7 +140,10 @@ namespace Orm::Query::Grammars QString whereNull(const WhereConditionItem &where) const; /*! Compile a "where not null" clause. */ QString whereNotNull(const WhereConditionItem &where) const; + /*! Compile a raw where clause. */ + QString whereRaw(const WhereConditionItem &where) const; + // CUR reorder silverqx /*! Compile the "order by" portions of the query. */ QString compileOrders(const QueryBuilder &query) const; /*! Compile the query orders to the vector. */ diff --git a/include/orm/query/querybuilder.hpp b/include/orm/query/querybuilder.hpp index f12d8c0ef..cdc59a128 100644 --- a/include/orm/query/querybuilder.hpp +++ b/include/orm/query/querybuilder.hpp @@ -144,13 +144,12 @@ namespace Query /*! Set the table which the query is targeting. */ Builder &from(Expression &&table); - /*! Set the table which the query is targeting. */ - Builder &fromRaw(const QString &expression, - const QVector &bindings = {}); - /*! Makes "from" fetch from a subquery. */ template Builder &fromSub(T &&query, const QString &as); + /*! Set the table which the query is targeting. */ + Builder &fromRaw(const QString &expression, + const QVector &bindings = {}); /*! Add a join clause to the query. */ template @@ -299,6 +298,12 @@ namespace Query /*! Add an "or where not null" clause to the query. */ Builder &orWhereNotNull(const Column &column); + /*! 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. */ @@ -307,6 +312,9 @@ namespace 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"); @@ -314,11 +322,20 @@ namespace 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 = "created_at"); /*! Add an "order by" clause for a timestamp to the query. */ diff --git a/include/orm/tiny/model.hpp b/include/orm/tiny/model.hpp index f0823af52..30fd961a0 100644 --- a/include/orm/tiny/model.hpp +++ b/include/orm/tiny/model.hpp @@ -499,6 +499,14 @@ namespace Relations { static std::unique_ptr> orWhereNotNull(const Column &column); + /*! Add a raw "where" clause to the query. */ + static std::unique_ptr> + whereRaw(const QString &sql, const QVector &bindings = {}, + const QString &condition = "and"); + /*! Add a raw "or where" clause to the query. */ + static std::unique_ptr> + orWhereRaw(const QString &sql, const QVector &bindings = {}); + /*! Add a "group by" clause to the query. */ static std::unique_ptr> groupBy(const QVector &groups); @@ -510,6 +518,10 @@ namespace Relations { static std::unique_ptr> groupBy(Args &&...groups); + /*! Add a raw "groupBy" clause to the query. */ + static std::unique_ptr> + groupByRaw(const QString &sql, const QVector &bindings = {}); + /*! Add a "having" clause to the query. */ static std::unique_ptr> having(const Column &column, const QString &comparison, @@ -519,6 +531,14 @@ namespace Relations { orHaving(const Column &column, const QString &comparison, const QVariant &value); + /*! Add a raw "having" clause to the query. */ + static std::unique_ptr> + havingRaw(const QString &sql, const QVector &bindings = {}, + const QString &condition = "and"); + /*! Add a raw "or having" clause to the query. */ + static std::unique_ptr> + orHavingRaw(const QString &sql, const QVector &bindings = {}); + /*! Add an "order by" clause to the query. */ static std::unique_ptr> orderBy(const Column &column, const QString &direction = "asc"); @@ -526,6 +546,10 @@ namespace Relations { static std::unique_ptr> orderByDesc(const Column &column); + /*! Add a raw "order by" clause to the query. */ + static std::unique_ptr> + orderByRaw(const QString &sql, const QVector &bindings = {}); + /*! Add an "order by" clause for a timestamp to the query. */ static std::unique_ptr> latest(const Column &column = ""); @@ -2379,6 +2403,31 @@ namespace Relations { return builder; } + template + std::unique_ptr> + Model::whereRaw( + const QString &sql, const QVector &bindings, + const QString &condition) + { + auto builder = query(); + + builder->whereRaw(sql, bindings, condition); + + return builder; + } + + template + std::unique_ptr> + Model::orWhereRaw( + const QString &sql, const QVector &bindings) + { + auto builder = query(); + + builder->whereRaw(sql, bindings, QStringLiteral("or")); + + return builder; + } + template std::unique_ptr> Model::groupBy(const QVector &groups) @@ -2401,6 +2450,18 @@ namespace Relations { return builder; } + template + std::unique_ptr> + Model::groupByRaw( + const QString &sql, const QVector &bindings) + { + auto builder = query(); + + builder->groupByRaw(sql, bindings); + + return builder; + } + template template std::unique_ptr> @@ -2438,6 +2499,31 @@ namespace Relations { return builder; } + template + std::unique_ptr> + Model::havingRaw( + const QString &sql, const QVector &bindings, + const QString &condition) + { + auto builder = query(); + + builder->havingRaw(sql, bindings, condition); + + return builder; + } + + template + std::unique_ptr> + Model::orHavingRaw( + const QString &sql, const QVector &bindings) + { + auto builder = query(); + + builder->havingRaw(sql, bindings, QStringLiteral("or")); + + return builder; + } + template std::unique_ptr> Model::orderBy(const Column &column, @@ -2461,6 +2547,18 @@ namespace Relations { return builder; } + template + std::unique_ptr> + Model::orderByRaw(const QString &sql, + const QVector &bindings) + { + auto builder = query(); + + builder->orderByRaw(sql, bindings); + + return builder; + } + template std::unique_ptr> Model::latest(const Column &column) diff --git a/include/orm/tiny/relations/relation.hpp b/include/orm/tiny/relations/relation.hpp index 2b23efc59..dab894f63 100644 --- a/include/orm/tiny/relations/relation.hpp +++ b/include/orm/tiny/relations/relation.hpp @@ -443,6 +443,14 @@ namespace Relations /*! Add an "or where not null" clause to the query. */ const Relation &orWhereNotNull(const Column &column) const; + /*! Add a raw "where" clause to the query. */ + const Relation &whereRaw(const QString &sql, + const QVector &bindings = {}, + const QString &condition = "and") const; + /*! Add a raw "or where" clause to the query. */ + const Relation &orWhereRaw(const QString &sql, + const QVector &bindings = {}) const; + /*! Add a "group by" clause to the query. */ const Relation &groupBy(const QVector &groups) const; /*! Add a "group by" clause to the query. */ @@ -451,6 +459,10 @@ namespace Relations template const Relation &groupBy(Args &&...groups) const; + /*! Add a raw "groupBy" clause to the query. */ + const Relation &groupByRaw(const QString &sql, + const QVector &bindings = {}) const; + /*! Add a "having" clause to the query. */ const Relation &having(const Column &column, const QString &comparison, const QVariant &value, @@ -459,12 +471,24 @@ namespace Relations const Relation &orHaving(const Column &column, const QString &comparison, const QVariant &value) const; + /*! Add a raw "having" clause to the query. */ + const Relation &havingRaw(const QString &sql, + const QVector &bindings = {}, + const QString &condition = "and") const; + /*! Add a raw "or having" clause to the query. */ + const Relation &orHavingRaw(const QString &sql, + const QVector &bindings = {}) const; + /*! Add an "order by" clause to the query. */ const Relation &orderBy(const Column &column, const QString &direction = "asc") const; /*! Add a descending "order by" clause to the query. */ const Relation &orderByDesc(const Column &column) const; + /*! Add a raw "order by" clause to the query. */ + const Relation &orderByRaw(const QString &sql, + const QVector &bindings = {}) const; + /*! Add an "order by" clause for a timestamp to the query. */ const Relation &latest(const Column &column = "") const; /*! Add an "order by" clause for a timestamp to the query. */ @@ -1466,6 +1490,27 @@ namespace Relations return *this; } + template + const Relation & + Relation::whereRaw( + const QString &sql, const QVector &bindings, + const QString &condition) const + { + m_query->whereRaw(sql, bindings, condition); + + return *this; + } + + template + const Relation & + Relation::orWhereRaw( + const QString &sql, const QVector &bindings) const + { + m_query->whereRaw(sql, bindings, QStringLiteral("or")); + + return *this; + } + template const Relation & Relation::groupBy(const QVector &groups) const @@ -1494,6 +1539,16 @@ namespace Relations return *this; } + template + const Relation & + Relation::groupByRaw(const QString &sql, + const QVector &bindings) const + { + m_query->groupByRaw(sql, bindings); + + return *this; + } + template const Relation & Relation::having( @@ -1515,6 +1570,27 @@ namespace Relations return *this; } + template + const Relation & + Relation::havingRaw( + const QString &sql, const QVector &bindings, + const QString &condition) const + { + m_query->havingRaw(sql, bindings, condition); + + return *this; + } + + template + const Relation & + Relation::orHavingRaw( + const QString &sql, const QVector &bindings) const + { + m_query->havingRaw(sql, bindings, QStringLiteral("or")); + + return *this; + } + template const Relation & Relation::orderBy(const Column &column, @@ -1534,6 +1610,16 @@ namespace Relations return *this; } + template + const Relation & + Relation::orderByRaw(const QString &sql, + const QVector &bindings) const + { + m_query->orderByRaw(sql, bindings); + + return *this; + } + template const Relation & Relation::latest(const Column &column) const diff --git a/include/orm/tiny/tinybuilder.hpp b/include/orm/tiny/tinybuilder.hpp index 4a1b0304b..fb48816b1 100644 --- a/include/orm/tiny/tinybuilder.hpp +++ b/include/orm/tiny/tinybuilder.hpp @@ -342,6 +342,12 @@ namespace Relations /*! Add an "or where not null" clause to the query. */ Builder &orWhereNotNull(const Column &column); + /*! 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. */ @@ -350,6 +356,9 @@ namespace Relations 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"); @@ -357,11 +366,20 @@ namespace Relations 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. */ @@ -1380,6 +1398,23 @@ namespace Relations 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, QStringLiteral("or")); + return *this; + } + template Builder &Builder::groupBy(const QVector &groups) { @@ -1402,6 +1437,14 @@ namespace Relations 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, @@ -1420,6 +1463,23 @@ namespace Relations 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, QStringLiteral("or")); + return *this; + } + template Builder & Builder::orderBy(const Column &column, const QString &direction) @@ -1435,6 +1495,14 @@ namespace Relations 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) diff --git a/src/orm/query/grammars/grammar.cpp b/src/orm/query/grammars/grammar.cpp index 7879d4308..6c455f813 100644 --- a/src/orm/query/grammars/grammar.cpp +++ b/src/orm/query/grammars/grammar.cpp @@ -284,10 +284,19 @@ QString Grammar::compileHavings(const QueryBuilder &query) const QString Grammar::compileHaving(const HavingConditionItem &having) const { + /* If the having clause is "raw", we can just return the clause straight away + without doing any more processing on it. Otherwise, we will compile the + clause into SQL based on the components that make it up from builder. */ switch (having.type) { + T_LIKELY case HavingType::BASIC: return compileBasicHaving(having); + T_UNLIKELY + case HavingType::RAW: + return QStringLiteral("%1 %2").arg(having.condition, having.sql); + + T_UNLIKELY default: throw RuntimeError(QStringLiteral("Unknown HavingType (%1).") .arg(static_cast(having.type))); @@ -357,6 +366,11 @@ QString Grammar::whereNotNull(const WhereConditionItem &where) const return QStringLiteral("%1 is not null").arg(wrap(where.column)); } +QString Grammar::whereRaw(const WhereConditionItem &where) const +{ + return where.sql; +} + QString Grammar::compileOrders(const QueryBuilder &query) const { if (query.getOrders().isEmpty()) @@ -373,8 +387,11 @@ QStringList Grammar::compileOrdersToVector(const QueryBuilder &query) const compiledOrders.reserve(orders.size()); for (const auto &order : orders) - compiledOrders << QStringLiteral("%1 %2") - .arg(wrap(order.column), order.direction.toLower()); + if (order.sql.isEmpty()) T_LIKELY + compiledOrders << QStringLiteral("%1 %2") + .arg(wrap(order.column), order.direction.toLower()); + else T_UNLIKELY + compiledOrders << order.sql; return compiledOrders; } diff --git a/src/orm/query/grammars/mysqlgrammar.cpp b/src/orm/query/grammars/mysqlgrammar.cpp index 9fb7eb398..3d61001af 100644 --- a/src/orm/query/grammars/mysqlgrammar.cpp +++ b/src/orm/query/grammars/mysqlgrammar.cpp @@ -155,6 +155,7 @@ MySqlGrammar::getWhereMethod(const WhereType whereType) const getBind(&MySqlGrammar::whereNotIn), getBind(&MySqlGrammar::whereNull), getBind(&MySqlGrammar::whereNotNull), + getBind(&MySqlGrammar::whereRaw), }; static const auto size = cached.size(); diff --git a/src/orm/query/grammars/postgresgrammar.cpp b/src/orm/query/grammars/postgresgrammar.cpp index 698c73d21..7b94e09eb 100644 --- a/src/orm/query/grammars/postgresgrammar.cpp +++ b/src/orm/query/grammars/postgresgrammar.cpp @@ -160,6 +160,7 @@ PostgresGrammar::getWhereMethod(const WhereType whereType) const getBind(&PostgresGrammar::whereNotIn), getBind(&PostgresGrammar::whereNull), getBind(&PostgresGrammar::whereNotNull), + getBind(&PostgresGrammar::whereRaw), }; static const auto size = cached.size(); diff --git a/src/orm/query/grammars/sqlitegrammar.cpp b/src/orm/query/grammars/sqlitegrammar.cpp index adf111661..97ad62e23 100644 --- a/src/orm/query/grammars/sqlitegrammar.cpp +++ b/src/orm/query/grammars/sqlitegrammar.cpp @@ -136,6 +136,7 @@ SQLiteGrammar::getWhereMethod(const WhereType whereType) const getBind(&SQLiteGrammar::whereNotIn), getBind(&SQLiteGrammar::whereNull), getBind(&SQLiteGrammar::whereNotNull), + getBind(&SQLiteGrammar::whereRaw), }; static const auto size = cached.size(); diff --git a/src/orm/query/querybuilder.cpp b/src/orm/query/querybuilder.cpp index 227d188a0..baf24b483 100644 --- a/src/orm/query/querybuilder.cpp +++ b/src/orm/query/querybuilder.cpp @@ -438,6 +438,21 @@ Builder &Builder::orWhereNotNull(const Column &column) return orWhereNotNull(QVector {column}); } +Builder &Builder::whereRaw(const QString &sql, const QVector &bindings, + const QString &condition) +{ + m_wheres.append({.condition = condition, .type = WhereType::RAW, .sql = sql}); + + addBinding(bindings, BindingType::WHERE); + + return *this; +} + +Builder &Builder::orWhereRaw(const QString &sql, const QVector &bindings) +{ + return whereRaw(sql, bindings, QStringLiteral("or")); +} + Builder &Builder::groupBy(const QVector &groups) { if (groups.isEmpty()) @@ -453,6 +468,15 @@ Builder &Builder::groupBy(const Column &group) return groupBy(QVector {group}); } +Builder &Builder::groupByRaw(const QString &sql, const QVector &bindings) +{ + m_groups.append(Expression(sql)); + + addBinding(bindings, BindingType::GROUPBY); + + return *this; +} + Builder &Builder::having(const Column &column, const QString &comparison, const QVariant &value, const QString &condition) { @@ -474,6 +498,21 @@ Builder &Builder::orHaving(const Column &column, const QString &comparison, return having(column, comparison, value, QStringLiteral("or")); } +Builder &Builder::havingRaw(const QString &sql, const QVector &bindings, + const QString &condition) +{ + m_havings.append({.condition = condition, .type = HavingType::RAW, .sql = sql}); + + addBinding(bindings, BindingType::HAVING); + + return *this; +} + +Builder &Builder::orHavingRaw(const QString &sql, const QVector &bindings) +{ + return havingRaw(sql, bindings, QStringLiteral("or")); +} + Builder &Builder::orderBy(const Column &column, const QString &direction) { const auto &directionLower = direction.toLower(); @@ -494,6 +533,15 @@ Builder &Builder::orderByDesc(const Column &column) return orderBy(column, QStringLiteral("desc")); } +Builder &Builder::orderByRaw(const QString &sql, const QVector &bindings) +{ + m_orders.append({.sql = sql}); + + addBinding(bindings, BindingType::ORDER); + + return *this; +} + Builder &Builder::latest(const Column &column) { /* Default value "created_at" is ok, because we are in the QueryBuilder,