drogon
C++14/17-based HTTP application framework
Loading...
Searching...
No Matches
BaseBuilder.h
Go to the documentation of this file.
1
14
15#pragma once
16
17#include <drogon/orm/Criteria.h>
18#include <drogon/orm/DbClient.h>
19#include <string_view>
20#include <future>
21#include <memory>
22#include <string>
23#include <type_traits>
24#include <utility>
25#include <vector>
26#include <optional>
27
28#define unimplemented() assert(false && "unimplemented")
29
30namespace drogon
31{
32namespace orm
33{
34inline std::string to_string(CompareOperator op)
35{
36 switch (op)
37 {
38 case CompareOperator::EQ:
39 return "=";
40 case CompareOperator::NE:
41 return "!=";
42 case CompareOperator::GT:
43 return ">";
44 case CompareOperator::GE:
45 return ">=";
46 case CompareOperator::LT:
47 return "<";
48 case CompareOperator::LE:
49 return "<=";
50 case CompareOperator::Like:
51 return "like";
52 case CompareOperator::NotLike:
53 case CompareOperator::In:
54 case CompareOperator::NotIn:
55 case CompareOperator::IsNull:
56 case CompareOperator::IsNotNull:
57 default:
58 unimplemented();
59 return "";
60 }
61}
62
63struct Filter
64{
65 std::string column;
66 CompareOperator op;
67 std::string value;
68};
69
73enum class JoinType
74{
75 InnerJoin,
76 LeftJoin,
77 RightJoin,
78 FullJoin
79};
80
81inline std::string to_join_string(JoinType type)
82{
83 switch (type)
84 {
85 case JoinType::InnerJoin:
86 return "INNER JOIN";
87 case JoinType::LeftJoin:
88 return "LEFT JOIN";
89 case JoinType::RightJoin:
90 return "RIGHT JOIN";
91 case JoinType::FullJoin:
92 return "FULL JOIN";
93 }
94 // Should never reach here
95 return "INNER JOIN";
96}
97
99{
100 JoinType type;
101 std::string table;
102 std::string onLeft; // e.g. "users.id"
103 std::string onRight; // e.g. "posts.user_id"
104};
105
116inline bool isValidSqlIdentifier(const std::string &identifier)
117{
118 if (identifier.empty())
119 {
120 return false;
121 }
122 for (auto c : identifier)
123 {
124 if (!std::isalnum(static_cast<unsigned char>(c)) && c != '_' &&
125 c != '.')
126 {
127 return false;
128 }
129 }
130 return true;
131}
132
133// Forward declaration to be a friend
134template <typename T, bool SelectAll, bool Single = false>
136
137template <typename T, bool SelectAll, bool Single = false>
139{
140 using ResultType =
141 std::conditional_t<SelectAll,
142 std::conditional_t<Single, T, std::vector<T>>,
143 std::conditional_t<Single, Row, Result>>;
144
145 // Make the constructor of `TransformBuilder<T, SelectAll, true>` through
146 // `TransformBuilder::single()` be able to read these protected members.
147 friend class TransformBuilder<T, SelectAll, true>;
148
149 protected:
150 std::string from_;
151 std::string columns_;
152 std::vector<Filter> filters_;
153 std::vector<JoinClause> joins_;
154 std::optional<std::uint64_t> limit_;
155 std::optional<std::uint64_t> offset_;
156 // The order is important; use vector<pair> instead of unordered_map and
157 // map.
158 std::vector<std::pair<std::string, bool>> orders_;
159
160 inline void assert_column(const std::string &colName) const
161 {
162 for (const typename T::MetaData &m : T::metaData_)
163 {
164 if (m.colName_ == colName)
165 {
166 return;
167 }
168 }
169 throw UsageError("The column `" + colName +
170 "` is not in the specified table.");
171 }
172
173 private:
179 inline std::string gen_sql(ClientType type) const noexcept
180 {
181 int pCount = 0;
182 const auto placeholder = [type, &pCount]() {
183 ++pCount;
184 return type == ClientType::PostgreSQL ? "$" + std::to_string(pCount)
185 : "?";
186 };
187
188 std::string sql = "select " + columns_ + " from " + from_;
189 for (const auto &join : joins_)
190 {
191 sql += " " + to_join_string(join.type) + " " + join.table + " ON " +
192 join.onLeft + " = " + join.onRight;
193 }
194 if (!filters_.empty())
195 {
196 sql += " where " + filters_[0].column + " " +
197 to_string(filters_[0].op) + " " + placeholder() + "";
198 for (int i = 1; i < filters_.size(); ++i)
199 {
200 sql += " and " + filters_[i].column + " " +
201 to_string(filters_[i].op) + " " + placeholder() + "";
202 }
203 }
204 if (!orders_.empty())
205 {
206 sql += " order by " + orders_[0].first + " " +
207 std::string(orders_[0].second ? "asc" : "desc");
208 for (int i = 1; i < orders_.size(); ++i)
209 {
210 sql += ", " + orders_[i].first + " " +
211 std::string(orders_[i].second ? "asc" : "desc");
212 }
213 }
214 if (limit_.has_value())
215 {
216 sql += " limit " + std::to_string(limit_.value());
217 }
218 if (offset_.has_value())
219 {
220 sql += " offset " + std::to_string(offset_.value());
221 }
222 return sql;
223 }
224
225 inline std::vector<std::string> gen_args() const noexcept
226 {
227 std::vector<std::string> args;
228 if (!filters_.empty())
229 {
230 for (const Filter &f : filters_)
231 {
232 args.emplace_back(f.value);
233 }
234 }
235 return args;
236 }
237
238 public:
239 static ResultType convert_result(const Result &r)
240 {
241 if constexpr (SelectAll)
242 {
243 if constexpr (Single)
244 {
245 return T(r[0]);
246 }
247 else
248 {
249 std::vector<T> ret;
250 for (const Row &row : r)
251 {
252 ret.emplace_back(T(row));
253 }
254 return ret;
255 }
256 }
257 else
258 {
259 if constexpr (Single)
260 {
261 return r[0];
262 }
263 else
264 {
265 return r;
266 }
267 }
268 }
269
270 inline ResultType execSync(const DbClientPtr &client)
271 {
272 Result r(nullptr);
273 {
274 auto binder = *client << gen_sql(client->type());
275 for (const std::string &a : gen_args())
276 {
277 binder << a;
278 }
279 binder << Mode::Blocking;
280 binder >> [&r](const Result &result) { r = result; };
281 binder.exec(); // exec may throw exception
282 }
283 return convert_result(r);
284 }
285
286 template <typename TFn, typename EFn>
287 void execAsync(const DbClientPtr &client,
288 TFn &&rCallback,
289 EFn &&exceptCallback) noexcept
290 {
291 auto binder = *client << gen_sql(client->type());
292 for (const std::string &a : gen_args())
293 {
294 binder << a;
295 }
296 binder >> std::forward<TFn>(rCallback);
297 binder >> std::forward<EFn>(exceptCallback);
298 }
299
300 inline std::future<ResultType> execAsyncFuture(
301 const DbClientPtr &client) noexcept
302 {
303 auto binder = *client << gen_sql(client->type());
304 for (const std::string &a : gen_args())
305 {
306 binder << a;
307 }
308 std::shared_ptr<std::promise<ResultType>> prom =
309 std::make_shared<std::promise<ResultType>>();
310 binder >>
311 [prom](const Result &r) { prom->set_value(convert_result(r)); };
312 binder >>
313 [prom](const std::exception_ptr &e) { prom->set_exception(e); };
314 binder.exec();
315 return prom->get_future();
316 }
317};
318} // namespace orm
319} // namespace drogon
JoinType
Represents a SQL JOIN clause.
Definition BaseBuilder.h:74
bool isValidSqlIdentifier(const std::string &identifier)
Validate that a string is a safe SQL identifier.
Definition BaseBuilder.h:116
Definition BaseBuilder.h:139
Result set containing data returned by a query or command.
Definition Result.h:58
Reference to one row in a result.
Definition Row.h:47
Definition BaseBuilder.h:135
Error in usage of drogon orm library, similar to std::logic_error.
Definition Exception.h:235
Drogon Test is a minimal effort test framework developed because the major C++ test frameworks doesn'...
Definition Attribute.h:23
Definition BaseBuilder.h:64
Definition BaseBuilder.h:99