Lightweight 0.20251202.0
Loading...
Searching...
No Matches
Core.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "../Api.hpp"
6#include "../SqlQueryFormatter.hpp"
7
8#include <algorithm>
9#include <concepts>
10#include <ranges>
11
12namespace Lightweight
13{
14
15/// @defgroup QueryBuilder Query Builder
16///
17/// @brief The query builder is a high level API for building SQL queries using high level C++ syntax.
18
19/// @brief SqlWildcardType is a placeholder for an explicit wildcard input parameter in a SQL query.
20///
21/// Use this in the SqlQueryBuilder::Where method to insert a '?' placeholder for a wildcard.
22///
23/// @ingroup QueryBuilder
24struct SqlWildcardType
25{
26};
27
28/// @brief SqlWildcard is a placeholder for an explicit wildcard input parameter in a SQL query.
29static constexpr inline auto SqlWildcard = SqlWildcardType {};
30
31/// @brief Name of table in a SQL query, where the table's name is aliased.
32struct AliasedTableName
33{
34 std::string_view tableName;
35 std::string_view alias;
36
37 std::weak_ordering operator<=>(AliasedTableName const&) const = default;
38};
39
40template <typename T>
41concept TableName =
42 std::convertible_to<T, std::string_view> || std::convertible_to<T, std::string> || std::same_as<T, AliasedTableName>;
43
44namespace detail
45{
46
47 struct RawSqlCondition
48 {
49 std::string condition;
50 };
51
52} // namespace detail
53
54/// @brief SqlQualifiedTableColumnName represents a column name qualified with a table name.
55/// @ingroup QueryBuilder
56struct SqlQualifiedTableColumnName
57{
58 std::string_view tableName;
59 std::string_view columnName;
60};
61
62/// @brief Helper function to create a SqlQualifiedTableColumnName from string_view
63///
64/// @param column The column name, which must be qualified with a table name.
65/// Example QualifiedColumnName<"Table.Column"> will create a SqlQualifiedTableColumnName with
66/// tableName = "Table" and columnName = "Column".
67template <Reflection::StringLiteral columnLiteral>
68constexpr SqlQualifiedTableColumnName QualifiedColumnName = []() consteval {
69#if !defined(_MSC_VER)
70 // enforce that we do not have symbols \ [ ] " '
71 static_assert(
72 !std::ranges::any_of(columnLiteral,
73 [](char c) consteval { return c == '\\' || c == '[' || c == ']' || c == '"' || c == '\''; }),
74 "QualifiedColumnName should not contain symbols \\ [ ] \" '");
75#endif
76
77 static_assert(std::ranges::count(columnLiteral, '.') == 1,
78 "QualifiedColumnName requires a column name with a single '.' to separate table and column name");
79 constexpr auto column = columnLiteral.sv();
80 auto dotPos = column.find('.');
81 return SqlQualifiedTableColumnName { .tableName = column.substr(0, dotPos), .columnName = column.substr(dotPos + 1) };
82}();
83
84namespace detail
85{
86
87 template <typename ColumnName>
88 std::string MakeSqlColumnName(ColumnName const& columnName)
89 {
90 using namespace std::string_view_literals;
91 std::string output;
92
93 if constexpr (std::is_same_v<ColumnName, SqlQualifiedTableColumnName>)
94 {
95 output.reserve(columnName.tableName.size() + columnName.columnName.size() + 5);
96 output += '"';
97 output += columnName.tableName;
98 output += R"(".")"sv;
99 output += columnName.columnName;
100 output += '"';
101 }
102 else if constexpr (std::is_same_v<ColumnName, SqlRawColumnNameView>)
103 {
104 output += columnName.value;
105 }
106 else if constexpr (std::is_same_v<ColumnName, SqlWildcardType>)
107 {
108 output += '?';
109 }
110 else
111 {
112 output += '"';
113 output += columnName;
114 output += '"';
115 }
116 return output;
117 }
118
119 template <typename T>
120 std::string MakeEscapedSqlString(T const& value)
121 {
122 std::string escapedValue;
123 escapedValue += '\'';
124
125 for (auto const ch: value)
126 {
127 // In SQL strings, single quotes are escaped by doubling them.
128 if (ch == '\'')
129 escapedValue += '\'';
130 escapedValue += ch;
131 }
132 escapedValue += '\'';
133 return escapedValue;
134 }
135
136} // namespace detail
137
138struct [[nodiscard]] SqlSearchCondition
139{
140 std::string tableName;
141 std::string tableAlias;
142 std::string tableJoins;
143 std::string condition;
144 std::vector<SqlVariant>* inputBindings = nullptr;
145};
146
147/// @brief Query builder for building JOIN conditions.
148/// @ingroup QueryBuilder
149class SqlJoinConditionBuilder
150{
151 public:
152 explicit SqlJoinConditionBuilder(std::string_view referenceTable, std::string* condition) noexcept:
153 _referenceTable { referenceTable },
154 _condition { *condition }
155 {
156 }
157
158 SqlJoinConditionBuilder& On(std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
159 {
160 return Operator(joinColumnName, onOtherColumn, "AND");
161 }
162
163 SqlJoinConditionBuilder& OrOn(std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
164 {
165 return Operator(joinColumnName, onOtherColumn, "OR");
166 }
167
168 SqlJoinConditionBuilder& Operator(std::string_view joinColumnName,
169 SqlQualifiedTableColumnName onOtherColumn,
170 std::string_view op)
171 {
172 if (_firstCall)
173 _firstCall = !_firstCall;
174 else
175 _condition += std::format(" {} ", op);
176
177 _condition += '"';
178 _condition += _referenceTable;
179 _condition += "\".\"";
180 _condition += joinColumnName;
181 _condition += "\" = \"";
182 _condition += onOtherColumn.tableName;
183 _condition += "\".\"";
184 _condition += onOtherColumn.columnName;
185 _condition += '"';
186
187 return *this;
188 }
189
190 private:
191 std::string_view _referenceTable;
192 std::string& _condition;
193 bool _firstCall = true;
194};
195
196/// Helper CRTP-based class for building WHERE clauses.
197///
198/// This class is inherited by the SqlSelectQueryBuilder, SqlUpdateQueryBuilder, and SqlDeleteQueryBuilder
199///
200/// @ingroup QueryBuilder
201template <typename Derived>
202class [[nodiscard]] SqlWhereClauseBuilder
203{
204 public:
205 /// Indicates, that the next WHERE clause should be AND-ed (default).
206 [[nodiscard]] Derived& And() noexcept;
207
208 /// Indicates, that the next WHERE clause should be OR-ed.
209 [[nodiscard]] Derived& Or() noexcept;
210
211 /// Indicates, that the next WHERE clause should be negated.
212 [[nodiscard]] Derived& Not() noexcept;
213
214 /// Constructs or extends a raw WHERE clause.
215 [[nodiscard]] Derived& WhereRaw(std::string_view sqlConditionExpression);
216
217 /// Constructs or extends a WHERE clause to test for a binary operation.
218 template <typename ColumnName, typename T>
219 [[nodiscard]] Derived& Where(ColumnName const& columnName, std::string_view binaryOp, T const& value);
220
221 /// Constructs or extends a WHERE clause to test for a binary operation for RHS as sub-select query.
222 template <typename ColumnName, typename SubSelectQuery>
223 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
224 [[nodiscard]] Derived& Where(ColumnName const& columnName, std::string_view binaryOp, SubSelectQuery const& value);
225
226 /// Constructs or extends a WHERE/OR clause to test for a binary operation.
227 template <typename ColumnName, typename T>
228 [[nodiscard]] Derived& OrWhere(ColumnName const& columnName, std::string_view binaryOp, T const& value);
229
230 /// Constructs or extends a WHERE clause to test for a binary operation for RHS as string literal.
231 template <typename ColumnName, std::size_t N>
232 Derived& Where(ColumnName const& columnName, std::string_view binaryOp, char const (&value)[N]);
233
234 /// Constructs or extends a WHERE clause to test for equality.
235 template <typename ColumnName, typename T>
236 [[nodiscard]] Derived& Where(ColumnName const& columnName, T const& value);
237
238 /// Constructs or extends an WHERE/OR clause to test for equality.
239 template <typename ColumnName, typename T>
240 [[nodiscard]] Derived& OrWhere(ColumnName const& columnName, T const& value);
241
242 /// Constructs or extends a WHERE/AND clause to test for a group of values.
243 template <typename Callable>
244 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
245 [[nodiscard]] Derived& Where(Callable const& callable);
246
247 /// Constructs or extends an WHERE/OR clause to test for a group of values.
248 template <typename Callable>
249 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
250 [[nodiscard]] Derived& OrWhere(Callable const& callable);
251
252 /// Constructs or extends an WHERE/OR clause to test for a value, satisfying std::ranges::input_range.
253 template <typename ColumnName, std::ranges::input_range InputRange>
254 [[nodiscard]] Derived& WhereIn(ColumnName const& columnName, InputRange const& values);
255
256 /// Constructs or extends an WHERE/OR clause to test for a value, satisfying std::initializer_list.
257 template <typename ColumnName, typename T>
258 [[nodiscard]] Derived& WhereIn(ColumnName const& columnName, std::initializer_list<T> const& values);
259
260 /// Constructs or extends an WHERE/OR clause to test for a value, satisfying a sub-select query.
261 template <typename ColumnName, typename SubSelectQuery>
262 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
263 [[nodiscard]] Derived& WhereIn(ColumnName const& columnName, SubSelectQuery const& subSelectQuery);
264
265 /// Constructs or extends an WHERE/OR clause to test for a value to be NULL.
266 template <typename ColumnName>
267 [[nodiscard]] Derived& WhereNull(ColumnName const& columnName);
268
269 /// Constructs or extends a WHERE clause to test for a value being not null.
270 template <typename ColumnName>
271 [[nodiscard]] Derived& WhereNotNull(ColumnName const& columnName);
272
273 /// Constructs or extends a WHERE clause to test for a value being equal to another column.
274 template <typename ColumnName, typename T>
275 [[nodiscard]] Derived& WhereNotEqual(ColumnName const& columnName, T const& value);
276
277 /// Constructs or extends a WHERE clause to test for a value being true.
278 template <typename ColumnName>
279 [[nodiscard]] Derived& WhereTrue(ColumnName const& columnName);
280
281 /// Constructs or extends a WHERE clause to test for a value being false.
282 template <typename ColumnName>
283 [[nodiscard]] Derived& WhereFalse(ColumnName const& columnName);
284
285 /// Construts or extends a WHERE clause to test for a binary operation between two columns.
286 template <typename LeftColumn, typename RightColumn>
287 [[nodiscard]] Derived& WhereColumn(LeftColumn const& left, std::string_view binaryOp, RightColumn const& right);
288
289 /// Constructs an INNER JOIN clause.
290 ///
291 /// @param joinTable The table's name to join with. This can be a string, a string_view, or an AliasedTableName.
292 /// @param joinColumnName The name of the column in the main table to join on.
293 /// @param onOtherColumn The column in the join table to compare against.
294 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable,
295 std::string_view joinColumnName,
296 SqlQualifiedTableColumnName onOtherColumn);
297
298 /// Constructs an INNER JOIN clause.
299 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable,
300 std::string_view joinColumnName,
301 std::string_view onMainTableColumn);
302
303 /// Constructs an INNER JOIN clause with a custom ON clause.
304 template <typename OnChainCallable>
305 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
306 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
307
308 /// Constructs an `INNER JOIN` clause given two fields from different records
309 /// using the field name as join column.
310 ///
311 /// @tparam LeftField The field name to join on, such as `JoinTestB::a_id`, which will join on table `JoinTestB` with
312 /// the column `a_id` to be compared against right field's column.
313 /// @tparam RightField The other column to compare and join against.
314 ///
315 /// Example:
316 /// @code
317 /// InnerJoin<&JoinTestB::a_id, &JoinTestA::id>()
318 /// // This will generate a INNER JOIN "JoinTestB" ON "InnerTestB"."a_id" = "JoinTestA"."id"
319 template <auto LeftField, auto RightField>
320 [[nodiscard]] Derived& InnerJoin();
321
322 /// Constructs an LEFT OUTER JOIN clause.
323 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable,
324 std::string_view joinColumnName,
325 SqlQualifiedTableColumnName onOtherColumn);
326
327 /// Constructs an LEFT OUTER JOIN clause.
328 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable,
329 std::string_view joinColumnName,
330 std::string_view onMainTableColumn);
331
332 /// Constructs an LEFT OUTER JOIN clause with a custom ON clause.
333 template <typename OnChainCallable>
334 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
335 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
336
337 /// Constructs an RIGHT OUTER JOIN clause.
338 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable,
339 std::string_view joinColumnName,
340 SqlQualifiedTableColumnName onOtherColumn);
341
342 /// Constructs an RIGHT OUTER JOIN clause.
343 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable,
344 std::string_view joinColumnName,
345 std::string_view onMainTableColumn);
346
347 /// Constructs an RIGHT OUTER JOIN clause with a custom ON clause.
348 template <typename OnChainCallable>
349 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
350 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
351
352 /// Constructs an FULL OUTER JOIN clause.
353 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable,
354 std::string_view joinColumnName,
355 SqlQualifiedTableColumnName onOtherColumn);
356
357 /// Constructs an FULL OUTER JOIN clause.
358 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable,
359 std::string_view joinColumnName,
360 std::string_view onMainTableColumn);
361
362 /// Constructs an FULL OUTER JOIN clause with a custom ON clause.
363 template <typename OnChainCallable>
364 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
365 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
366
367 private:
368 SqlSearchCondition& SearchCondition() noexcept;
369 [[nodiscard]] SqlQueryFormatter const& Formatter() const noexcept;
370
371 enum class WhereJunctor : uint8_t
372 {
373 Null,
374 Where,
375 And,
376 Or,
377 };
378
379 WhereJunctor m_nextWhereJunctor = WhereJunctor::Where;
380 bool m_nextIsNot = false;
381
382 void AppendWhereJunctor();
383
384 template <typename ColumnName>
385 requires(std::same_as<ColumnName, SqlQualifiedTableColumnName> || std::convertible_to<ColumnName, std::string_view>
386 || std::same_as<ColumnName, SqlRawColumnNameView> || std::convertible_to<ColumnName, std::string>)
387 void AppendColumnName(ColumnName const& columnName);
388
389 template <typename LiteralType>
390 void AppendLiteralValue(LiteralType const& value);
391
392 template <typename LiteralType, typename TargetType>
393 void PopulateLiteralValueInto(LiteralType const& value, TargetType& target);
394
395 template <typename LiteralType>
396 detail::RawSqlCondition PopulateSqlSetExpression(LiteralType const& values);
397
398 enum class JoinType : uint8_t
399 {
400 INNER,
401 LEFT,
402 RIGHT,
403 FULL
404 };
405
406 /// Constructs a JOIN clause.
407 [[nodiscard]] Derived& Join(JoinType joinType,
408 TableName auto joinTable,
409 std::string_view joinColumnName,
410 SqlQualifiedTableColumnName onOtherColumn);
411
412 /// Constructs a JOIN clause.
413 [[nodiscard]] Derived& Join(JoinType joinType,
414 TableName auto joinTable,
415 std::string_view joinColumnName,
416 std::string_view onMainTableColumn);
417
418 /// Constructs a JOIN clause.
419 template <typename OnChainCallable>
420 [[nodiscard]] Derived& Join(JoinType joinType, TableName auto joinTable, OnChainCallable const& onClauseBuilder);
421};
422
423enum class SqlResultOrdering : uint8_t
424{
425 ASCENDING,
426 DESCENDING
427};
428
429namespace detail
430{
431 enum class SelectType : std::uint8_t
432 {
433 Undefined,
434 Count,
435 All,
436 First,
437 Range
438 };
439
440 struct ComposedQuery
441 {
442 SelectType selectType = SelectType::Undefined;
443 SqlQueryFormatter const* formatter = nullptr;
444
445 bool distinct = false;
446 SqlSearchCondition searchCondition {};
447
448 std::string fields;
449
450 std::string orderBy;
451 std::string groupBy;
452
453 size_t offset = 0;
454 size_t limit = (std::numeric_limits<size_t>::max)();
455
456 [[nodiscard]] LIGHTWEIGHT_API std::string ToSql() const;
457 };
458} // namespace detail
459
460template <typename Derived>
461class [[nodiscard]] SqlBasicSelectQueryBuilder: public SqlWhereClauseBuilder<Derived>
462{
463 public:
464 /// Adds a DISTINCT clause to the SELECT query.
465 Derived& Distinct() noexcept;
466
467 /// Constructs or extends a ORDER BY clause.
468 Derived& OrderBy(SqlQualifiedTableColumnName const& columnName,
469 SqlResultOrdering ordering = SqlResultOrdering::ASCENDING);
470
471 /// Constructs or extends a ORDER BY clause.
472 Derived& OrderBy(std::string_view columnName, SqlResultOrdering ordering = SqlResultOrdering::ASCENDING);
473
474 /// Constructs or extends a GROUP BY clause.
475 Derived& GroupBy(std::string_view columnName);
476
477 using ComposedQuery = detail::ComposedQuery;
478
479 protected:
480 ComposedQuery _query {}; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
481};
482
483template <typename Derived>
484inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::Distinct() noexcept
485{
486 _query.distinct = true;
487 return static_cast<Derived&>(*this);
488}
489
490template <typename Derived>
491inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::OrderBy(std::string_view columnName,
492 SqlResultOrdering ordering)
493{
494 if (_query.orderBy.empty())
495 _query.orderBy += "\n ORDER BY ";
496 else
497 _query.orderBy += ", ";
498
499 _query.orderBy += '"';
500 _query.orderBy += columnName;
501 _query.orderBy += '"';
502
503 if (ordering == SqlResultOrdering::DESCENDING)
504 _query.orderBy += " DESC";
505 else if (ordering == SqlResultOrdering::ASCENDING)
506 _query.orderBy += " ASC";
507
508 return static_cast<Derived&>(*this);
509}
510
511template <typename Derived>
512inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::OrderBy(
513 SqlQualifiedTableColumnName const& columnName, SqlResultOrdering ordering)
514{
515 if (_query.orderBy.empty())
516 _query.orderBy += "\n ORDER BY ";
517 else
518 _query.orderBy += ", ";
519
520 _query.orderBy += '"';
521 _query.orderBy += columnName.tableName;
522 _query.orderBy += "\".\"";
523 _query.orderBy += columnName.columnName;
524 _query.orderBy += '"';
525
526 if (ordering == SqlResultOrdering::DESCENDING)
527 _query.orderBy += " DESC";
528 else if (ordering == SqlResultOrdering::ASCENDING)
529 _query.orderBy += " ASC";
530
531 return static_cast<Derived&>(*this);
532}
533
534template <typename Derived>
535inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::GroupBy(std::string_view columnName)
536{
537 if (_query.groupBy.empty())
538 _query.groupBy += "\n GROUP BY ";
539 else
540 _query.groupBy += ", ";
541
542 _query.groupBy += '"';
543 _query.groupBy += columnName;
544 _query.groupBy += '"';
545
546 return static_cast<Derived&>(*this);
547}
548
549template <typename Derived>
550inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::And() noexcept
551{
552 m_nextWhereJunctor = WhereJunctor::And;
553 return static_cast<Derived&>(*this);
554}
555
556template <typename Derived>
557inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Or() noexcept
558{
559 m_nextWhereJunctor = WhereJunctor::Or;
560 return static_cast<Derived&>(*this);
561}
562
563template <typename Derived>
564inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Not() noexcept
565{
566 m_nextIsNot = !m_nextIsNot;
567 return static_cast<Derived&>(*this);
568}
569
570template <typename Derived>
571template <typename ColumnName, typename T>
572inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName, T const& value)
573{
574 if constexpr (detail::OneOf<T, SqlNullType, std::nullopt_t>)
575 {
576 if (m_nextIsNot)
577 {
578 m_nextIsNot = false;
579 return Where(columnName, "IS NOT", value);
580 }
581 else
582 return Where(columnName, "IS", value);
583 }
584 else
585 return Where(columnName, "=", value);
586}
587
588template <typename Derived>
589template <typename ColumnName, typename T>
590inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(ColumnName const& columnName,
591 T const& value)
592{
593 return Or().Where(columnName, value);
594}
595
596template <typename Derived>
597template <typename Callable>
598 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
599inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(Callable const& callable)
600{
601 return Or().Where(callable);
602}
603
604template <typename Derived>
605template <typename Callable>
606 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
607inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(Callable const& callable)
608{
609 auto& condition = SearchCondition().condition;
610
611 auto const originalSize = condition.size();
612
613 AppendWhereJunctor();
614 m_nextWhereJunctor = WhereJunctor::Null;
615 condition += '(';
616
617 auto const sizeBeforeCallable = condition.size();
618
619 (void) callable(*this);
620
621 if (condition.size() == sizeBeforeCallable)
622 condition.resize(originalSize);
623 else
624 condition += ')';
625
626 return static_cast<Derived&>(*this);
627}
628
629template <typename Derived>
630template <typename ColumnName, std::ranges::input_range InputRange>
631inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
632 InputRange const& values)
633{
634 if (values.empty())
635 return static_cast<Derived&>(*this);
636 return Where(columnName, "IN", PopulateSqlSetExpression(values));
637}
638
639template <typename Derived>
640template <typename ColumnName, typename T>
641inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
642 std::initializer_list<T> const& values)
643{
644 if (values.begin() == values.end())
645 return static_cast<Derived&>(*this);
646 return Where(columnName, "IN", PopulateSqlSetExpression(values));
647}
648
649template <typename Derived>
650template <typename ColumnName, typename SubSelectQuery>
651 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
652inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
653 SubSelectQuery const& subSelectQuery)
654{
655 return Where(columnName, "IN", detail::RawSqlCondition { "(" + subSelectQuery.ToSql() + ")" });
656}
657
658template <typename Derived>
659template <typename ColumnName>
660inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotNull(ColumnName const& columnName)
661{
662 return Where(columnName, "IS NOT", detail::RawSqlCondition { "NULL" });
663}
664
665template <typename Derived>
666template <typename ColumnName>
667inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNull(ColumnName const& columnName)
668{
669 return Where(columnName, "IS", detail::RawSqlCondition { "NULL" });
670}
671
672template <typename Derived>
673template <typename ColumnName, typename T>
674inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotEqual(ColumnName const& columnName,
675 T const& value)
676{
677 if constexpr (detail::OneOf<T, SqlNullType, std::nullopt_t>)
678 return Where(columnName, "IS NOT", value);
679 else
680 return Where(columnName, "!=", value);
681}
682
683template <typename Derived>
684template <typename ColumnName>
685inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereTrue(ColumnName const& columnName)
686{
687 return Where(columnName, "=", true);
688}
689
690template <typename Derived>
691template <typename ColumnName>
692inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereFalse(ColumnName const& columnName)
693{
694 return Where(columnName, "=", false);
695}
696
697template <typename Derived>
698template <typename LeftColumn, typename RightColumn>
699inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereColumn(LeftColumn const& left,
700 std::string_view binaryOp,
701 RightColumn const& right)
702{
703 AppendWhereJunctor();
704
705 AppendColumnName(left);
706 SearchCondition().condition += ' ';
707 SearchCondition().condition += binaryOp;
708 SearchCondition().condition += ' ';
709 AppendColumnName(right);
710
711 return static_cast<Derived&>(*this);
712}
713
714template <typename T>
715struct WhereConditionLiteralType
716{
717 constexpr static bool needsQuotes = !std::is_integral_v<T> && !std::is_floating_point_v<T> && !std::same_as<T, bool>
718 && !std::same_as<T, SqlWildcardType>;
719};
720
721template <typename Derived>
722template <typename ColumnName, std::size_t N>
723inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
724 std::string_view binaryOp,
725 char const (&value)[N])
726{
727 return Where(columnName, binaryOp, std::string_view { value, N - 1 });
728}
729
730template <typename Derived>
731template <typename ColumnName, typename T>
732inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
733 std::string_view binaryOp,
734 T const& value)
735{
736 auto& searchCondition = SearchCondition();
737
738 AppendWhereJunctor();
739 AppendColumnName(columnName);
740 searchCondition.condition += ' ';
741 searchCondition.condition += binaryOp;
742 searchCondition.condition += ' ';
743 AppendLiteralValue(value);
744
745 return static_cast<Derived&>(*this);
746}
747
748template <typename Derived>
749template <typename ColumnName, typename SubSelectQuery>
750 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
751inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
752 std::string_view binaryOp,
753 SubSelectQuery const& value)
754{
755 return Where(columnName, binaryOp, detail::RawSqlCondition { "(" + value.ToSql() + ")" });
756}
757
758template <typename Derived>
759template <typename ColumnName, typename T>
760inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(ColumnName const& columnName,
761 std::string_view binaryOp,
762 T const& value)
763{
764 return Or().Where(columnName, binaryOp, value);
765}
766
767template <typename Derived>
768inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
769 std::string_view joinColumnName,
770 SqlQualifiedTableColumnName onOtherColumn)
771{
772 return Join(JoinType::INNER, joinTable, joinColumnName, onOtherColumn);
773}
774
775template <typename Derived>
776inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
777 std::string_view joinColumnName,
778 std::string_view onMainTableColumn)
779{
780 return Join(JoinType::INNER, joinTable, joinColumnName, onMainTableColumn);
781}
782
783template <typename Derived>
784template <auto LeftField, auto RightField>
785Derived& SqlWhereClauseBuilder<Derived>::InnerJoin()
786{
787#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
788 return Join(JoinType::INNER,
789 RecordTableName<MemberClassType<LeftField>>,
790 FieldNameOf<LeftField>,
791 SqlQualifiedTableColumnName { RecordTableName<MemberClassType<RightField>>, FieldNameOf<RightField> });
792#else
793 return Join(
794 JoinType::INNER,
795 RecordTableName<Reflection::MemberClassType<LeftField>>,
796 FieldNameOf<LeftField>,
797 SqlQualifiedTableColumnName { RecordTableName<Reflection::MemberClassType<RightField>>, FieldNameOf<RightField> });
798#endif
799}
800
801template <typename Derived>
802template <typename OnChainCallable>
803 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
804Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
805{
806 return Join(JoinType::INNER, joinTable, onClauseBuilder);
807}
808
809template <typename Derived>
810inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(
811 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
812{
813 return Join(JoinType::LEFT, joinTable, joinColumnName, onOtherColumn);
814}
815
816template <typename Derived>
817inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable,
818 std::string_view joinColumnName,
819 std::string_view onMainTableColumn)
820{
821 return Join(JoinType::LEFT, joinTable, joinColumnName, onMainTableColumn);
822}
823
824template <typename Derived>
825template <typename OnChainCallable>
826 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
827Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
828{
829 return Join(JoinType::LEFT, joinTable, onClauseBuilder);
830}
831
832template <typename Derived>
833inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(
834 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
835{
836 return Join(JoinType::RIGHT, joinTable, joinColumnName, onOtherColumn);
837}
838
839template <typename Derived>
840inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable,
841 std::string_view joinColumnName,
842 std::string_view onMainTableColumn)
843{
844 return Join(JoinType::RIGHT, joinTable, joinColumnName, onMainTableColumn);
845}
846
847template <typename Derived>
848template <typename OnChainCallable>
849 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
850Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
851{
852 return Join(JoinType::RIGHT, joinTable, onClauseBuilder);
853}
854
855template <typename Derived>
856inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(
857 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
858{
859 return Join(JoinType::FULL, joinTable, joinColumnName, onOtherColumn);
860}
861
862template <typename Derived>
863inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable,
864 std::string_view joinColumnName,
865 std::string_view onMainTableColumn)
866{
867 return Join(JoinType::FULL, joinTable, joinColumnName, onMainTableColumn);
868}
869
870template <typename Derived>
871template <typename OnChainCallable>
872 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
873Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
874{
875 return Join(JoinType::FULL, joinTable, onClauseBuilder);
876}
877
878template <typename Derived>
879inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereRaw(std::string_view sqlConditionExpression)
880{
881 AppendWhereJunctor();
882
883 auto& condition = SearchCondition().condition;
884 condition += sqlConditionExpression;
885
886 return static_cast<Derived&>(*this);
887}
888
889template <typename Derived>
890inline LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SqlWhereClauseBuilder<Derived>::SearchCondition() noexcept
891{
892 return static_cast<Derived*>(this)->SearchCondition();
893}
894
895template <typename Derived>
896inline LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& SqlWhereClauseBuilder<Derived>::Formatter() const noexcept
897{
898 return static_cast<Derived const*>(this)->Formatter();
899}
900
901template <typename Derived>
902inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendWhereJunctor()
903{
904 using namespace std::string_view_literals;
905
906 auto& condition = SearchCondition().condition;
907
908 switch (m_nextWhereJunctor)
909 {
910 case WhereJunctor::Null:
911 break;
912 case WhereJunctor::Where:
913 condition += "\n WHERE "sv;
914 break;
915 case WhereJunctor::And:
916 condition += " AND "sv;
917 break;
918 case WhereJunctor::Or:
919 condition += " OR "sv;
920 break;
921 }
922
923 if (m_nextIsNot)
924 {
925 condition += "NOT "sv;
926 m_nextIsNot = false;
927 }
928
929 m_nextWhereJunctor = WhereJunctor::And;
930}
931
932template <typename Derived>
933template <typename ColumnName>
934 requires(std::same_as<ColumnName, SqlQualifiedTableColumnName> || std::convertible_to<ColumnName, std::string_view>
935 || std::same_as<ColumnName, SqlRawColumnNameView> || std::convertible_to<ColumnName, std::string>)
936inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendColumnName(ColumnName const& columnName)
937{
938 SearchCondition().condition += detail::MakeSqlColumnName(columnName);
939}
940
941template <typename Derived>
942template <typename LiteralType>
943inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendLiteralValue(LiteralType const& value)
944{
945 auto& searchCondition = SearchCondition();
946
947 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>
948 || detail::OneOf<LiteralType, SqlNullType, std::nullopt_t> || std::is_same_v<LiteralType, SqlWildcardType>
949 || std::is_same_v<LiteralType, detail::RawSqlCondition>)
950 {
951 PopulateLiteralValueInto(value, searchCondition.condition);
952 }
953 else if (searchCondition.inputBindings)
954 {
955 searchCondition.condition += '?';
956 searchCondition.inputBindings->emplace_back(value);
957 }
958 else if constexpr (std::is_same_v<LiteralType, bool>)
959 {
960 searchCondition.condition += Formatter().BooleanLiteral(value);
961 }
962 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
963 {
964 searchCondition.condition += std::format("{}", value);
965 }
966 else
967 {
968 searchCondition.condition += detail::MakeEscapedSqlString(std::format("{}", value));
969 }
970}
971
972template <typename Derived>
973template <typename LiteralType, typename TargetType>
974inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::PopulateLiteralValueInto(LiteralType const& value,
975 TargetType& target)
976{
977 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>)
978 {
979 target += '"';
980 target += value.tableName;
981 target += "\".\"";
982 target += value.columnName;
983 target += '"';
984 }
985 else if constexpr (detail::OneOf<LiteralType, SqlNullType, std::nullopt_t>)
986 {
987 target += "NULL";
988 }
989 else if constexpr (std::is_same_v<LiteralType, SqlWildcardType>)
990 {
991 target += '?';
992 }
993 else if constexpr (std::is_same_v<LiteralType, detail::RawSqlCondition>)
994 {
995 target += value.condition;
996 }
997 else if constexpr (std::is_same_v<LiteralType, bool>)
998 {
999 target += Formatter().BooleanLiteral(value);
1000 }
1001 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
1002 {
1003 target += std::format("{}", value);
1004 }
1005 else
1006 {
1007 target += detail::MakeEscapedSqlString(std::format("{}", value));
1008 }
1009}
1010
1011template <typename Derived>
1012template <typename LiteralType>
1013detail::RawSqlCondition SqlWhereClauseBuilder<Derived>::PopulateSqlSetExpression(LiteralType const& values)
1014{
1015 using namespace std::string_view_literals;
1016 std::ostringstream fragment;
1017 fragment << '(';
1018#if !defined(__cpp_lib_ranges_enumerate)
1019 int index { -1 };
1020 for (auto const& value: values)
1021 {
1022 ++index;
1023#else
1024 for (auto const&& [index, value]: values | std::views::enumerate)
1025 {
1026#endif
1027 if (index > 0)
1028 fragment << ", "sv;
1029
1030 std::string valueString;
1031 PopulateLiteralValueInto(value, valueString);
1032 fragment << valueString;
1033 }
1034 fragment << ')';
1035 return detail::RawSqlCondition { fragment.str() };
1036}
1037
1038template <typename Derived>
1039inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1040 TableName auto joinTable,
1041 std::string_view joinColumnName,
1042 SqlQualifiedTableColumnName onOtherColumn)
1043{
1044 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1045 "INNER",
1046 "LEFT OUTER",
1047 "RIGHT OUTER",
1048 "FULL OUTER",
1049 };
1050
1051 if constexpr (std::is_same_v<std::remove_cvref_t<decltype(joinTable)>, AliasedTableName>)
1052 {
1053 SearchCondition().tableJoins += std::format("\n"
1054 R"( {0} JOIN "{1}" AS "{2}" ON "{2}"."{3}" = "{4}"."{5}")",
1055 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1056 joinTable.tableName,
1057 joinTable.alias,
1058 joinColumnName,
1059 onOtherColumn.tableName,
1060 onOtherColumn.columnName);
1061 }
1062 else
1063 {
1064 SearchCondition().tableJoins += std::format("\n"
1065 R"( {0} JOIN "{1}" ON "{1}"."{2}" = "{3}"."{4}")",
1066 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1067 joinTable,
1068 joinColumnName,
1069 onOtherColumn.tableName,
1070 onOtherColumn.columnName);
1071 }
1072 return static_cast<Derived&>(*this);
1073}
1074
1075template <typename Derived>
1076inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1077 TableName auto joinTable,
1078 std::string_view joinColumnName,
1079 std::string_view onMainTableColumn)
1080{
1081 return Join(joinType,
1082 joinTable,
1083 joinColumnName,
1084 SqlQualifiedTableColumnName { .tableName = SearchCondition().tableName, .columnName = onMainTableColumn });
1085}
1086
1087template <typename Derived>
1088template <typename Callable>
1089inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1090 TableName auto joinTable,
1091 Callable const& onClauseBuilder)
1092{
1093 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1094 "INNER",
1095 "LEFT OUTER",
1096 "RIGHT OUTER",
1097 "FULL OUTER",
1098 };
1099
1100 size_t const originalSize = SearchCondition().tableJoins.size();
1101 SearchCondition().tableJoins +=
1102 std::format("\n {0} JOIN \"{1}\" ON ", JoinTypeStrings[static_cast<std::size_t>(joinType)], joinTable);
1103 size_t const sizeBefore = SearchCondition().tableJoins.size();
1104 onClauseBuilder(SqlJoinConditionBuilder { joinTable, &SearchCondition().tableJoins });
1105 size_t const sizeAfter = SearchCondition().tableJoins.size();
1106 if (sizeBefore == sizeAfter)
1107 SearchCondition().tableJoins.resize(originalSize);
1108
1109 return static_cast<Derived&>(*this);
1110}
1111
1112} // namespace Lightweight
constexpr std::string_view RecordTableName
Holds the SQL tabl ename for the given record type.
Definition Utils.hpp:176