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