Lightweight 0.20250904.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 return Where(columnName, "IN", PopulateSqlSetExpression(values));
635}
636
637template <typename Derived>
638template <typename ColumnName, typename T>
639inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
640 std::initializer_list<T> const& values)
641{
642 return Where(columnName, "IN", PopulateSqlSetExpression(values));
643}
644
645template <typename Derived>
646template <typename ColumnName, typename SubSelectQuery>
647 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
648inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
649 SubSelectQuery const& subSelectQuery)
650{
651 return Where(columnName, "IN", detail::RawSqlCondition { "(" + subSelectQuery.ToSql() + ")" });
652}
653
654template <typename Derived>
655template <typename ColumnName>
656inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotNull(ColumnName const& columnName)
657{
658 return Where(columnName, "IS NOT", detail::RawSqlCondition { "NULL" });
659}
660
661template <typename Derived>
662template <typename ColumnName>
663inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNull(ColumnName const& columnName)
664{
665 return Where(columnName, "IS", detail::RawSqlCondition { "NULL" });
666}
667
668template <typename Derived>
669template <typename ColumnName, typename T>
670inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotEqual(ColumnName const& columnName,
671 T const& value)
672{
673 if constexpr (detail::OneOf<T, SqlNullType, std::nullopt_t>)
674 return Where(columnName, "IS NOT", value);
675 else
676 return Where(columnName, "!=", value);
677}
678
679template <typename Derived>
680template <typename ColumnName>
681inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereTrue(ColumnName const& columnName)
682{
683 return Where(columnName, "=", true);
684}
685
686template <typename Derived>
687template <typename ColumnName>
688inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereFalse(ColumnName const& columnName)
689{
690 return Where(columnName, "=", false);
691}
692
693template <typename Derived>
694template <typename LeftColumn, typename RightColumn>
695inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereColumn(LeftColumn const& left,
696 std::string_view binaryOp,
697 RightColumn const& right)
698{
699 AppendWhereJunctor();
700
701 AppendColumnName(left);
702 SearchCondition().condition += ' ';
703 SearchCondition().condition += binaryOp;
704 SearchCondition().condition += ' ';
705 AppendColumnName(right);
706
707 return static_cast<Derived&>(*this);
708}
709
710template <typename T>
711struct WhereConditionLiteralType
712{
713 constexpr static bool needsQuotes = !std::is_integral_v<T> && !std::is_floating_point_v<T> && !std::same_as<T, bool>
714 && !std::same_as<T, SqlWildcardType>;
715};
716
717template <typename Derived>
718template <typename ColumnName, std::size_t N>
719inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
720 std::string_view binaryOp,
721 char const (&value)[N])
722{
723 return Where(columnName, binaryOp, std::string_view { value, N - 1 });
724}
725
726template <typename Derived>
727template <typename ColumnName, typename T>
728inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
729 std::string_view binaryOp,
730 T const& value)
731{
732 auto& searchCondition = SearchCondition();
733
734 AppendWhereJunctor();
735 AppendColumnName(columnName);
736 searchCondition.condition += ' ';
737 searchCondition.condition += binaryOp;
738 searchCondition.condition += ' ';
739 AppendLiteralValue(value);
740
741 return static_cast<Derived&>(*this);
742}
743
744template <typename Derived>
745template <typename ColumnName, typename SubSelectQuery>
746 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
747inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
748 std::string_view binaryOp,
749 SubSelectQuery const& value)
750{
751 return Where(columnName, binaryOp, detail::RawSqlCondition { "(" + value.ToSql() + ")" });
752}
753
754template <typename Derived>
755template <typename ColumnName, typename T>
756inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(ColumnName const& columnName,
757 std::string_view binaryOp,
758 T const& value)
759{
760 return Or().Where(columnName, binaryOp, value);
761}
762
763template <typename Derived>
764inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
765 std::string_view joinColumnName,
766 SqlQualifiedTableColumnName onOtherColumn)
767{
768 return Join(JoinType::INNER, joinTable, joinColumnName, onOtherColumn);
769}
770
771template <typename Derived>
772inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
773 std::string_view joinColumnName,
774 std::string_view onMainTableColumn)
775{
776 return Join(JoinType::INNER, joinTable, joinColumnName, onMainTableColumn);
777}
778
779template <typename Derived>
780template <auto LeftField, auto RightField>
781Derived& SqlWhereClauseBuilder<Derived>::InnerJoin()
782{
783#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
784 return Join(JoinType::INNER,
785 RecordTableName<MemberClassType<LeftField>>,
786 FieldNameOf<LeftField>,
787 SqlQualifiedTableColumnName { RecordTableName<MemberClassType<RightField>>, FieldNameOf<RightField> });
788#else
789 return Join(
790 JoinType::INNER,
791 RecordTableName<Reflection::MemberClassType<LeftField>>,
792 FieldNameOf<LeftField>,
793 SqlQualifiedTableColumnName { RecordTableName<Reflection::MemberClassType<RightField>>, FieldNameOf<RightField> });
794#endif
795}
796
797template <typename Derived>
798template <typename OnChainCallable>
799 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
800Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
801{
802 return Join(JoinType::INNER, joinTable, onClauseBuilder);
803}
804
805template <typename Derived>
806inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(
807 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
808{
809 return Join(JoinType::LEFT, joinTable, joinColumnName, onOtherColumn);
810}
811
812template <typename Derived>
813inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable,
814 std::string_view joinColumnName,
815 std::string_view onMainTableColumn)
816{
817 return Join(JoinType::LEFT, joinTable, joinColumnName, onMainTableColumn);
818}
819
820template <typename Derived>
821template <typename OnChainCallable>
822 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
823Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
824{
825 return Join(JoinType::LEFT, joinTable, onClauseBuilder);
826}
827
828template <typename Derived>
829inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(
830 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
831{
832 return Join(JoinType::RIGHT, joinTable, joinColumnName, onOtherColumn);
833}
834
835template <typename Derived>
836inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable,
837 std::string_view joinColumnName,
838 std::string_view onMainTableColumn)
839{
840 return Join(JoinType::RIGHT, joinTable, joinColumnName, onMainTableColumn);
841}
842
843template <typename Derived>
844template <typename OnChainCallable>
845 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
846Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
847{
848 return Join(JoinType::RIGHT, joinTable, onClauseBuilder);
849}
850
851template <typename Derived>
852inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(
853 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
854{
855 return Join(JoinType::FULL, joinTable, joinColumnName, onOtherColumn);
856}
857
858template <typename Derived>
859inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable,
860 std::string_view joinColumnName,
861 std::string_view onMainTableColumn)
862{
863 return Join(JoinType::FULL, joinTable, joinColumnName, onMainTableColumn);
864}
865
866template <typename Derived>
867template <typename OnChainCallable>
868 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
869Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
870{
871 return Join(JoinType::FULL, joinTable, onClauseBuilder);
872}
873
874template <typename Derived>
875inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereRaw(std::string_view sqlConditionExpression)
876{
877 AppendWhereJunctor();
878
879 auto& condition = SearchCondition().condition;
880 condition += sqlConditionExpression;
881
882 return static_cast<Derived&>(*this);
883}
884
885template <typename Derived>
886inline LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SqlWhereClauseBuilder<Derived>::SearchCondition() noexcept
887{
888 return static_cast<Derived*>(this)->SearchCondition();
889}
890
891template <typename Derived>
892inline LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& SqlWhereClauseBuilder<Derived>::Formatter() const noexcept
893{
894 return static_cast<Derived const*>(this)->Formatter();
895}
896
897template <typename Derived>
898inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendWhereJunctor()
899{
900 using namespace std::string_view_literals;
901
902 auto& condition = SearchCondition().condition;
903
904 switch (m_nextWhereJunctor)
905 {
906 case WhereJunctor::Null:
907 break;
908 case WhereJunctor::Where:
909 condition += "\n WHERE "sv;
910 break;
911 case WhereJunctor::And:
912 condition += " AND "sv;
913 break;
914 case WhereJunctor::Or:
915 condition += " OR "sv;
916 break;
917 }
918
919 if (m_nextIsNot)
920 {
921 condition += "NOT "sv;
922 m_nextIsNot = false;
923 }
924
925 m_nextWhereJunctor = WhereJunctor::And;
926}
927
928template <typename Derived>
929template <typename ColumnName>
930 requires(std::same_as<ColumnName, SqlQualifiedTableColumnName> || std::convertible_to<ColumnName, std::string_view>
931 || std::same_as<ColumnName, SqlRawColumnNameView> || std::convertible_to<ColumnName, std::string>)
932inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendColumnName(ColumnName const& columnName)
933{
934 SearchCondition().condition += detail::MakeSqlColumnName(columnName);
935}
936
937template <typename Derived>
938template <typename LiteralType>
939inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendLiteralValue(LiteralType const& value)
940{
941 auto& searchCondition = SearchCondition();
942
943 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>
944 || detail::OneOf<LiteralType, SqlNullType, std::nullopt_t> || std::is_same_v<LiteralType, SqlWildcardType>
945 || std::is_same_v<LiteralType, detail::RawSqlCondition>)
946 {
947 PopulateLiteralValueInto(value, searchCondition.condition);
948 }
949 else if (searchCondition.inputBindings)
950 {
951 searchCondition.condition += '?';
952 searchCondition.inputBindings->emplace_back(value);
953 }
954 else if constexpr (std::is_same_v<LiteralType, bool>)
955 {
956 searchCondition.condition += Formatter().BooleanLiteral(value);
957 }
958 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
959 {
960 searchCondition.condition += std::format("{}", value);
961 }
962 else
963 {
964 searchCondition.condition += detail::MakeEscapedSqlString(std::format("{}", value));
965 }
966}
967
968template <typename Derived>
969template <typename LiteralType, typename TargetType>
970inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::PopulateLiteralValueInto(LiteralType const& value,
971 TargetType& target)
972{
973 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>)
974 {
975 target += '"';
976 target += value.tableName;
977 target += "\".\"";
978 target += value.columnName;
979 target += '"';
980 }
981 else if constexpr (detail::OneOf<LiteralType, SqlNullType, std::nullopt_t>)
982 {
983 target += "NULL";
984 }
985 else if constexpr (std::is_same_v<LiteralType, SqlWildcardType>)
986 {
987 target += '?';
988 }
989 else if constexpr (std::is_same_v<LiteralType, detail::RawSqlCondition>)
990 {
991 target += value.condition;
992 }
993 else if constexpr (std::is_same_v<LiteralType, bool>)
994 {
995 target += Formatter().BooleanLiteral(value);
996 }
997 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
998 {
999 target += std::format("{}", value);
1000 }
1001 else
1002 {
1003 target += detail::MakeEscapedSqlString(std::format("{}", value));
1004 }
1005}
1006
1007template <typename Derived>
1008template <typename LiteralType>
1009detail::RawSqlCondition SqlWhereClauseBuilder<Derived>::PopulateSqlSetExpression(LiteralType const& values)
1010{
1011 using namespace std::string_view_literals;
1012 std::ostringstream fragment;
1013 fragment << '(';
1014#if !defined(__cpp_lib_ranges_enumerate)
1015 int index { -1 };
1016 for (auto const& value: values)
1017 {
1018 ++index;
1019#else
1020 for (auto const&& [index, value]: values | std::views::enumerate)
1021 {
1022#endif
1023 if (index > 0)
1024 fragment << ", "sv;
1025
1026 std::string valueString;
1027 PopulateLiteralValueInto(value, valueString);
1028 fragment << valueString;
1029 }
1030 fragment << ')';
1031 return detail::RawSqlCondition { fragment.str() };
1032}
1033
1034template <typename Derived>
1035inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1036 TableName auto joinTable,
1037 std::string_view joinColumnName,
1038 SqlQualifiedTableColumnName onOtherColumn)
1039{
1040 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1041 "INNER",
1042 "LEFT OUTER",
1043 "RIGHT OUTER",
1044 "FULL OUTER",
1045 };
1046
1047 if constexpr (std::is_same_v<std::remove_cvref_t<decltype(joinTable)>, AliasedTableName>)
1048 {
1049 SearchCondition().tableJoins += std::format("\n"
1050 R"( {0} JOIN "{1}" AS "{2}" ON "{2}"."{3}" = "{4}"."{5}")",
1051 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1052 joinTable.tableName,
1053 joinTable.alias,
1054 joinColumnName,
1055 onOtherColumn.tableName,
1056 onOtherColumn.columnName);
1057 }
1058 else
1059 {
1060 SearchCondition().tableJoins += std::format("\n"
1061 R"( {0} JOIN "{1}" ON "{1}"."{2}" = "{3}"."{4}")",
1062 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1063 joinTable,
1064 joinColumnName,
1065 onOtherColumn.tableName,
1066 onOtherColumn.columnName);
1067 }
1068 return static_cast<Derived&>(*this);
1069}
1070
1071template <typename Derived>
1072inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1073 TableName auto joinTable,
1074 std::string_view joinColumnName,
1075 std::string_view onMainTableColumn)
1076{
1077 return Join(joinType,
1078 joinTable,
1079 joinColumnName,
1080 SqlQualifiedTableColumnName { .tableName = SearchCondition().tableName, .columnName = onMainTableColumn });
1081}
1082
1083template <typename Derived>
1084template <typename Callable>
1085inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1086 TableName auto joinTable,
1087 Callable const& onClauseBuilder)
1088{
1089 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1090 "INNER",
1091 "LEFT OUTER",
1092 "RIGHT OUTER",
1093 "FULL OUTER",
1094 };
1095
1096 size_t const originalSize = SearchCondition().tableJoins.size();
1097 SearchCondition().tableJoins +=
1098 std::format("\n {0} JOIN \"{1}\" ON ", JoinTypeStrings[static_cast<std::size_t>(joinType)], joinTable);
1099 size_t const sizeBefore = SearchCondition().tableJoins.size();
1100 onClauseBuilder(SqlJoinConditionBuilder { joinTable, &SearchCondition().tableJoins });
1101 size_t const sizeAfter = SearchCondition().tableJoins.size();
1102 if (sizeBefore == sizeAfter)
1103 SearchCondition().tableJoins.resize(originalSize);
1104
1105 return static_cast<Derived&>(*this);
1106}
1107
1108} // namespace Lightweight
constexpr std::string_view RecordTableName
Holds the SQL tabl ename for the given record type.
Definition Utils.hpp:176