Lightweight 0.20260617.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 /// Construts or extends a WHERE clause to test for a binary operation between two columns.
310 template <typename LeftColumn, typename RightColumn>
311 [[nodiscard]] Derived& WhereColumn(LeftColumn const& left, std::string_view binaryOp, RightColumn const& right);
312
313 /// Constructs an INNER JOIN clause.
314 ///
315 /// @param joinTable The table's name to join with. This can be a string, a string_view, or an AliasedTableName.
316 /// @param joinColumnName The name of the column in the main table to join on.
317 /// @param onOtherColumn The column in the join table to compare against.
318 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable,
319 std::string_view joinColumnName,
320 SqlQualifiedTableColumnName onOtherColumn);
321
322 /// Constructs an INNER JOIN clause.
323 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable,
324 std::string_view joinColumnName,
325 std::string_view onMainTableColumn);
326
327 /// Constructs an INNER JOIN clause with a custom ON clause.
328 template <typename OnChainCallable>
329 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
330 [[nodiscard]] Derived& InnerJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
331
332 /// Constructs an `INNER JOIN` clause given two fields from different records
333 /// using the field name as join column.
334 ///
335 /// @tparam LeftField The field name to join on, such as `JoinTestB::a_id`, which will join on table `JoinTestB` with
336 /// the column `a_id` to be compared against right field's column.
337 /// @tparam RightField The other column to compare and join against.
338 ///
339 /// Example:
340 /// @code
341 /// InnerJoin<&JoinTestB::a_id, &JoinTestA::id>()
342 /// // This will generate a INNER JOIN "JoinTestB" ON "InnerTestB"."a_id" = "JoinTestA"."id"
343 /// @endcode
344 template <auto LeftField, auto RightField>
345 [[nodiscard]] Derived& InnerJoin();
346
347 /// Constructs an LEFT OUTER JOIN clause.
348 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable,
349 std::string_view joinColumnName,
350 SqlQualifiedTableColumnName onOtherColumn);
351
352 /// Constructs an LEFT OUTER JOIN clause.
353 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable,
354 std::string_view joinColumnName,
355 std::string_view onMainTableColumn);
356
357 /// Constructs an LEFT OUTER JOIN clause with a custom ON clause.
358 template <typename OnChainCallable>
359 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
360 [[nodiscard]] Derived& LeftOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
361
362 /// Constructs an RIGHT OUTER JOIN clause.
363 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable,
364 std::string_view joinColumnName,
365 SqlQualifiedTableColumnName onOtherColumn);
366
367 /// Constructs an RIGHT OUTER JOIN clause.
368 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable,
369 std::string_view joinColumnName,
370 std::string_view onMainTableColumn);
371
372 /// Constructs an RIGHT OUTER JOIN clause with a custom ON clause.
373 template <typename OnChainCallable>
374 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
375 [[nodiscard]] Derived& RightOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
376
377 /// Constructs an FULL OUTER JOIN clause.
378 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable,
379 std::string_view joinColumnName,
380 SqlQualifiedTableColumnName onOtherColumn);
381
382 /// Constructs an FULL OUTER JOIN clause.
383 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable,
384 std::string_view joinColumnName,
385 std::string_view onMainTableColumn);
386
387 /// Constructs an FULL OUTER JOIN clause with a custom ON clause.
388 template <typename OnChainCallable>
389 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
390 [[nodiscard]] Derived& FullOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder);
391
392 private:
393 SqlSearchCondition& SearchCondition() noexcept;
394 [[nodiscard]] SqlQueryFormatter const& Formatter() const noexcept;
395
396 enum class WhereJunctor : uint8_t
397 {
398 Null,
399 Where,
400 And,
401 Or,
402 };
403
404 WhereJunctor m_nextWhereJunctor = WhereJunctor::Where;
405 bool m_nextIsNot = false;
406
407 void AppendWhereJunctor();
408
409 /// Appends a column name to the WHERE condition.
410 template <typename ColumnName>
411 requires(std::same_as<ColumnName, SqlQualifiedTableColumnName> || std::convertible_to<ColumnName, std::string_view>
412 || std::convertible_to<ColumnName, std::string>)
413 void AppendColumnName(ColumnName const& columnName);
414
415 /// Appends a literal value to the WHERE condition.
416 template <typename LiteralType>
417 void AppendLiteralValue(LiteralType const& value);
418
419 /// Populates a literal value into the target string.
420 template <typename LiteralType, typename TargetType>
421 void PopulateLiteralValueInto(LiteralType const& value, TargetType& target);
422
423 template <typename LiteralType>
424 detail::RawSqlCondition PopulateSqlSetExpression(LiteralType const& values);
425
426 enum class JoinType : uint8_t
427 {
428 INNER,
429 LEFT,
430 RIGHT,
431 FULL
432 };
433
434 /// Constructs a JOIN clause.
435 [[nodiscard]] Derived& Join(JoinType joinType,
436 TableName auto joinTable,
437 std::string_view joinColumnName,
438 SqlQualifiedTableColumnName onOtherColumn);
439
440 /// Constructs a JOIN clause.
441 [[nodiscard]] Derived& Join(JoinType joinType,
442 TableName auto joinTable,
443 std::string_view joinColumnName,
444 std::string_view onMainTableColumn);
445
446 /// Constructs a JOIN clause.
447 template <typename OnChainCallable>
448 [[nodiscard]] Derived& Join(JoinType joinType, TableName auto joinTable, OnChainCallable const& onClauseBuilder);
449};
450
451enum class SqlResultOrdering : uint8_t
452{
453 ASCENDING,
454 DESCENDING
455};
456
457namespace detail
458{
459 enum class SelectType : std::uint8_t
460 {
461 Undefined,
462 Count,
463 All,
464 First,
465 Range
466 };
467
468 struct ComposedQuery
469 {
470 SelectType selectType = SelectType::Undefined;
471 SqlQueryFormatter const* formatter = nullptr;
472
473 bool distinct = false;
474 SqlSearchCondition searchCondition {};
475
476 std::string fields;
477
478 std::string orderBy;
479 std::string groupBy;
480
481 size_t offset = 0;
482 size_t limit = (std::numeric_limits<size_t>::max)();
483
484 [[nodiscard]] LIGHTWEIGHT_API std::string ToSql() const;
485 };
486} // namespace detail
487
488template <typename Derived>
489class [[nodiscard]] SqlBasicSelectQueryBuilder: public SqlWhereClauseBuilder<Derived>
490{
491 public:
492 /// Adds a DISTINCT clause to the SELECT query.
493 Derived& Distinct() noexcept;
494
495 /// Constructs or extends a ORDER BY clause.
496 Derived& OrderBy(SqlQualifiedTableColumnName const& columnName,
497 SqlResultOrdering ordering = SqlResultOrdering::ASCENDING);
498
499 /// Constructs or extends a ORDER BY clause.
500 Derived& OrderBy(std::string_view columnName, SqlResultOrdering ordering = SqlResultOrdering::ASCENDING);
501
502 /// Constructs or extends a GROUP BY clause.
503 Derived& GroupBy(std::string_view columnName);
504
505 /// Constructs or extends a GROUP BY clause with a qualified column name.
506 Derived& GroupBy(SqlQualifiedTableColumnName const& columnName);
507
508 using ComposedQuery = detail::ComposedQuery;
509
510 protected:
511 // mutable so const finalizers / projection-helpers can delegate to the
512 // non-const implementations via const_cast (idiomatic builder pattern —
513 // the observable const-state is the produced SQL, not the accumulator).
514 mutable ComposedQuery _query {}; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes)
515};
516
517template <typename Derived>
518inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::Distinct() noexcept
519{
520 _query.distinct = true;
521 return static_cast<Derived&>(*this);
522}
523
524template <typename Derived>
525inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::OrderBy(std::string_view columnName,
526 SqlResultOrdering ordering)
527{
528 if (_query.orderBy.empty())
529 _query.orderBy += "\n ORDER BY ";
530 else
531 _query.orderBy += ", ";
532
533 _query.orderBy += '"';
534 _query.orderBy += columnName;
535 _query.orderBy += '"';
536
537 if (ordering == SqlResultOrdering::DESCENDING)
538 _query.orderBy += " DESC";
539 else if (ordering == SqlResultOrdering::ASCENDING)
540 _query.orderBy += " ASC";
541
542 return static_cast<Derived&>(*this);
543}
544
545template <typename Derived>
546inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::OrderBy(
547 SqlQualifiedTableColumnName const& columnName, SqlResultOrdering ordering)
548{
549 if (_query.orderBy.empty())
550 _query.orderBy += "\n ORDER BY ";
551 else
552 _query.orderBy += ", ";
553
554 _query.orderBy += '"';
555 _query.orderBy += columnName.tableName;
556 _query.orderBy += "\".\"";
557 _query.orderBy += columnName.columnName;
558 _query.orderBy += '"';
559
560 if (ordering == SqlResultOrdering::DESCENDING)
561 _query.orderBy += " DESC";
562 else if (ordering == SqlResultOrdering::ASCENDING)
563 _query.orderBy += " ASC";
564
565 return static_cast<Derived&>(*this);
566}
567
568template <typename Derived>
569inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::GroupBy(std::string_view columnName)
570{
571 if (_query.groupBy.empty())
572 _query.groupBy += "\n GROUP BY ";
573 else
574 _query.groupBy += ", ";
575
576 _query.groupBy += '"';
577 _query.groupBy += columnName;
578 _query.groupBy += '"';
579
580 return static_cast<Derived&>(*this);
581}
582
583template <typename Derived>
584inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlBasicSelectQueryBuilder<Derived>::GroupBy(
585 SqlQualifiedTableColumnName const& columnName)
586{
587 if (_query.groupBy.empty())
588 _query.groupBy += "\n GROUP BY ";
589 else
590 _query.groupBy += ", ";
591
592 _query.groupBy += '"';
593 _query.groupBy += columnName.tableName;
594 _query.groupBy += "\".\"";
595 _query.groupBy += columnName.columnName;
596 _query.groupBy += '"';
597
598 return static_cast<Derived&>(*this);
599}
600
601template <typename Derived>
602inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::And() noexcept
603{
604 m_nextWhereJunctor = WhereJunctor::And;
605 return static_cast<Derived&>(*this);
606}
607
608template <typename Derived>
609inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Or() noexcept
610{
611 m_nextWhereJunctor = WhereJunctor::Or;
612 return static_cast<Derived&>(*this);
613}
614
615template <typename Derived>
616inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Not() noexcept
617{
618 m_nextIsNot = !m_nextIsNot;
619 return static_cast<Derived&>(*this);
620}
621
622/// Constructs or extends a WHERE clause to test for equality.
623template <typename Derived>
624template <typename ColumnName, typename T>
625inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName, T const& value)
626{
627 if constexpr (detail::OneOf<T, SqlNullType, std::nullopt_t>)
628 {
629 if (m_nextIsNot)
630 {
631 m_nextIsNot = false;
632 return Where(columnName, "IS NOT", value);
633 }
634 else
635 return Where(columnName, "IS", value);
636 }
637 else
638 return Where(columnName, "=", value);
639}
640
641/// Constructs or extends a WHERE/OR clause to test for equality.
642template <typename Derived>
643template <typename ColumnName, typename T>
644inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(ColumnName const& columnName,
645 T const& value)
646{
647 return Or().Where(columnName, value);
648}
649
650/// Constructs or extends a WHERE/OR clause to test for a group of values.
651template <typename Derived>
652template <typename Callable>
653 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
654inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(Callable const& callable)
655{
656 return Or().Where(callable);
657}
658
659/// Constructs or extends a WHERE/AND clause to test for a group of values.
660template <typename Derived>
661template <typename Callable>
662 requires std::invocable<Callable, SqlWhereClauseBuilder<Derived>&>
663inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(Callable const& callable)
664{
665 auto& condition = SearchCondition().condition;
666
667 auto const originalSize = condition.size();
668
669 AppendWhereJunctor();
670 m_nextWhereJunctor = WhereJunctor::Null;
671 condition += '(';
672
673 auto const sizeBeforeCallable = condition.size();
674
675 (void) callable(*this);
676
677 if (condition.size() == sizeBeforeCallable)
678 condition.resize(originalSize);
679 else
680 condition += ')';
681
682 return static_cast<Derived&>(*this);
683}
684
685/// Constructs or extends a WHERE IN clause with an input range.
686template <typename Derived>
687template <typename ColumnName, std::ranges::input_range InputRange>
688inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
689 InputRange const& values)
690{
691 if (values.empty())
692 return static_cast<Derived&>(*this);
693 return Where(columnName, "IN", PopulateSqlSetExpression(values));
694}
695
696/// Constructs or extends a WHERE IN clause with an initializer list.
697template <typename Derived>
698template <typename ColumnName, typename T>
699inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
700 std::initializer_list<T> const& values)
701{
702 if (values.begin() == values.end())
703 return static_cast<Derived&>(*this);
704 return Where(columnName, "IN", PopulateSqlSetExpression(values));
705}
706
707/// Constructs or extends a WHERE IN clause with a sub-select query.
708template <typename Derived>
709template <typename ColumnName, typename SubSelectQuery>
710 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
711inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereIn(ColumnName const& columnName,
712 SubSelectQuery const& subSelectQuery)
713{
714 return Where(columnName, "IN", detail::RawSqlCondition { "(" + subSelectQuery.ToSql() + ")" });
715}
716
717/// Constructs or extends a WHERE clause to test for a value being not null.
718template <typename Derived>
719template <typename ColumnName>
720inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotNull(ColumnName const& columnName)
721{
722 return Where(columnName, "IS NOT", detail::RawSqlCondition { "NULL" });
723}
724
725/// Constructs or extends a WHERE clause to test for a value being null.
726template <typename Derived>
727template <typename ColumnName>
728inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNull(ColumnName const& columnName)
729{
730 return Where(columnName, "IS", detail::RawSqlCondition { "NULL" });
731}
732
733/// Constructs or extends a WHERE clause to test for inequality.
734template <typename Derived>
735template <typename ColumnName, typename T>
736inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereNotEqual(ColumnName const& columnName,
737 T const& value)
738{
739 if constexpr (detail::OneOf<T, SqlNullType, std::nullopt_t>)
740 return Where(columnName, "IS NOT", value);
741 else
742 return Where(columnName, "!=", value);
743}
744
745/// Constructs or extends a WHERE clause to test for a value being true.
746template <typename Derived>
747template <typename ColumnName>
748inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereTrue(ColumnName const& columnName)
749{
750 return Where(columnName, "=", true);
751}
752
753/// Constructs or extends a WHERE clause to test for a value being false.
754template <typename Derived>
755template <typename ColumnName>
756inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereFalse(ColumnName const& columnName)
757{
758 return Where(columnName, "=", false);
759}
760
761/// Constructs or extends a WHERE clause to compare two columns.
762template <typename Derived>
763template <typename LeftColumn, typename RightColumn>
764inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereColumn(LeftColumn const& left,
765 std::string_view binaryOp,
766 RightColumn const& right)
767{
768 AppendWhereJunctor();
769
770 AppendColumnName(left);
771 SearchCondition().condition += ' ';
772 SearchCondition().condition += binaryOp;
773 SearchCondition().condition += ' ';
774 AppendColumnName(right);
775
776 return static_cast<Derived&>(*this);
777}
778
779template <typename T>
780struct WhereConditionLiteralType
781{
782 constexpr static bool needsQuotes = !std::is_integral_v<T> && !std::is_floating_point_v<T> && !std::same_as<T, bool>
783 && !std::same_as<T, SqlWildcardType>;
784};
785
786/// Constructs or extends a WHERE clause with a string literal value.
787template <typename Derived>
788template <typename ColumnName, std::size_t N>
789inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
790 std::string_view binaryOp,
791 char const (&value)[N])
792{
793 return Where(columnName, binaryOp, std::string_view { value, N - 1 });
794}
795
796/// Constructs or extends a WHERE clause to test for a binary operation.
797template <typename Derived>
798template <typename ColumnName, typename T>
799inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
800 std::string_view binaryOp,
801 T const& value)
802{
803 auto& searchCondition = SearchCondition();
804
805 AppendWhereJunctor();
806 AppendColumnName(columnName);
807 searchCondition.condition += ' ';
808 searchCondition.condition += binaryOp;
809 searchCondition.condition += ' ';
810 AppendLiteralValue(value);
811
812 return static_cast<Derived&>(*this);
813}
814
815/// Constructs or extends a WHERE clause with a sub-select query.
816template <typename Derived>
817template <typename ColumnName, typename SubSelectQuery>
818 requires(std::is_invocable_r_v<std::string, decltype(&SubSelectQuery::ToSql), SubSelectQuery const&>)
819inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Where(ColumnName const& columnName,
820 std::string_view binaryOp,
821 SubSelectQuery const& value)
822{
823 return Where(columnName, binaryOp, detail::RawSqlCondition { "(" + value.ToSql() + ")" });
824}
825
826/// Constructs or extends a WHERE/OR clause with a binary operation.
827template <typename Derived>
828template <typename ColumnName, typename T>
829inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::OrWhere(ColumnName const& columnName,
830 std::string_view binaryOp,
831 T const& value)
832{
833 return Or().Where(columnName, binaryOp, value);
834}
835
836template <typename Derived>
837inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
838 std::string_view joinColumnName,
839 SqlQualifiedTableColumnName onOtherColumn)
840{
841 return Join(JoinType::INNER, joinTable, joinColumnName, onOtherColumn);
842}
843
844template <typename Derived>
845inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable,
846 std::string_view joinColumnName,
847 std::string_view onMainTableColumn)
848{
849 return Join(JoinType::INNER, joinTable, joinColumnName, onMainTableColumn);
850}
851
852template <typename Derived>
853template <auto LeftField, auto RightField>
855{
856#if defined(LIGHTWEIGHT_CXX26_REFLECTION)
857 return Join(JoinType::INNER,
858 RecordTableName<MemberClassType<LeftField>>,
859 FieldNameOf<LeftField>,
860 SqlQualifiedTableColumnName { RecordTableName<MemberClassType<RightField>>, FieldNameOf<RightField> });
861#else
862 return Join(
863 JoinType::INNER,
864 RecordTableName<Reflection::MemberClassType<LeftField>>,
865 FieldNameOf<LeftField>,
866 SqlQualifiedTableColumnName { RecordTableName<Reflection::MemberClassType<RightField>>, FieldNameOf<RightField> });
867#endif
868}
869
870template <typename Derived>
871template <typename OnChainCallable>
872 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
873Derived& SqlWhereClauseBuilder<Derived>::InnerJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
874{
875 return Join(JoinType::INNER, joinTable, onClauseBuilder);
876}
877
878template <typename Derived>
879inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(
880 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
881{
882 return Join(JoinType::LEFT, joinTable, joinColumnName, onOtherColumn);
883}
884
885template <typename Derived>
886inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable,
887 std::string_view joinColumnName,
888 std::string_view onMainTableColumn)
889{
890 return Join(JoinType::LEFT, joinTable, joinColumnName, onMainTableColumn);
891}
892
893template <typename Derived>
894template <typename OnChainCallable>
895 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
896Derived& SqlWhereClauseBuilder<Derived>::LeftOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
897{
898 return Join(JoinType::LEFT, joinTable, onClauseBuilder);
899}
900
901template <typename Derived>
902inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(
903 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
904{
905 return Join(JoinType::RIGHT, joinTable, joinColumnName, onOtherColumn);
906}
907
908template <typename Derived>
909inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable,
910 std::string_view joinColumnName,
911 std::string_view onMainTableColumn)
912{
913 return Join(JoinType::RIGHT, joinTable, joinColumnName, onMainTableColumn);
914}
915
916template <typename Derived>
917template <typename OnChainCallable>
918 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
919Derived& SqlWhereClauseBuilder<Derived>::RightOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
920{
921 return Join(JoinType::RIGHT, joinTable, onClauseBuilder);
922}
923
924template <typename Derived>
925inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(
926 TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
927{
928 return Join(JoinType::FULL, joinTable, joinColumnName, onOtherColumn);
929}
930
931template <typename Derived>
932inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable,
933 std::string_view joinColumnName,
934 std::string_view onMainTableColumn)
935{
936 return Join(JoinType::FULL, joinTable, joinColumnName, onMainTableColumn);
937}
938
939template <typename Derived>
940template <typename OnChainCallable>
941 requires std::invocable<OnChainCallable, SqlJoinConditionBuilder>
942Derived& SqlWhereClauseBuilder<Derived>::FullOuterJoin(TableName auto joinTable, OnChainCallable const& onClauseBuilder)
943{
944 return Join(JoinType::FULL, joinTable, onClauseBuilder);
945}
946
947namespace detail
948{
949
950 /// @brief Sub-builder returned by `SqlWhereClauseBuilder::If`.
951 ///
952 /// Captures the underlying builder and a gating `std::optional`. Calling
953 /// `ThenWhere(column)` or `ThenWhere(column, binaryOp)` commits the chain:
954 /// when the optional holds a value it appends `WHERE column = *value` (or
955 /// `WHERE column <binaryOp> *value` for the explicit-operator overload);
956 /// either way it returns the underlying builder so further methods can be
957 /// chained.
958 template <typename Derived, typename T>
959 class [[nodiscard]] ConditionalWhereBuilder
960 {
961 public:
962 /// Constructs the sub-builder, binding to the underlying builder and the gating optional.
963 constexpr ConditionalWhereBuilder(Derived& builder, std::optional<T> const& value) noexcept:
964 _builder { builder },
965 _value { value }
966 {
967 }
968
969 /// Commits the conditional WHERE for @p column. Appends
970 /// `WHERE column = *value` when the gating optional holds a value;
971 /// otherwise the builder is left untouched.
972 /// @param column The column name (string, `SqlQualifiedTableColumnName`, etc.).
973 /// @return Reference to the underlying query builder.
974 template <typename ColumnName>
975 [[nodiscard]] Derived& ThenWhere(ColumnName const& column) const
976 {
977 if (_value.has_value())
978 return _builder.Where(column, *_value);
979 return _builder;
980 }
981
982 /// Commits the conditional WHERE for @p column using an explicit binary
983 /// operator. Appends `WHERE column <binaryOp> *value` when the gating
984 /// optional holds a value; otherwise the builder is left untouched.
985 /// Mirrors the `Where(column, binaryOp, value)` overload — use it for
986 /// range-style filters such as `">="`, `"<"`, `"!="`, or `"LIKE"`.
987 /// @param column The column name (string, `SqlQualifiedTableColumnName`, etc.).
988 /// @param binaryOp The SQL binary operator (e.g. `">="`, `"<"`, `"!="`).
989 /// @return Reference to the underlying query builder.
990 ///
991 /// Example:
992 /// @code
993 /// std::optional<SqlDateTime> since = ...;
994 /// std::optional<SqlDateTime> until = ...;
995 /// q.FromTable("Events").Select().Field("id")
996 /// .If(since).ThenWhere(FullyQualifiedNameOf<&Events::createdAt>, ">=")
997 /// .If(until).ThenWhere(FullyQualifiedNameOf<&Events::createdAt>, "<")
998 /// .All();
999 /// @endcode
1000 template <typename ColumnName>
1001 [[nodiscard]] Derived& ThenWhere(ColumnName const& column, std::string_view binaryOp) const
1002 {
1003 if (_value.has_value())
1004 return _builder.Where(column, binaryOp, *_value);
1005 return _builder;
1006 }
1007
1008 private:
1009 Derived& _builder;
1010 std::optional<T> const& _value;
1011 };
1012
1013} // namespace detail
1014
1015/// Starts a conditional WHERE chain gated by a `std::optional` value.
1016template <typename Derived>
1017template <typename T>
1018inline LIGHTWEIGHT_FORCE_INLINE auto SqlWhereClauseBuilder<Derived>::If(std::optional<T> const& value) noexcept
1019{
1020 return detail::ConditionalWhereBuilder<Derived, T> { static_cast<Derived&>(*this), value };
1021}
1022
1023template <typename Derived>
1024inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::WhereRaw(std::string_view sqlConditionExpression)
1025{
1026 AppendWhereJunctor();
1027
1028 auto& condition = SearchCondition().condition;
1029 condition += sqlConditionExpression;
1030
1031 return static_cast<Derived&>(*this);
1032}
1033
1034template <typename Derived>
1035inline LIGHTWEIGHT_FORCE_INLINE SqlSearchCondition& SqlWhereClauseBuilder<Derived>::SearchCondition() noexcept
1036{
1037 return static_cast<Derived*>(this)->SearchCondition();
1038}
1039
1040template <typename Derived>
1041inline LIGHTWEIGHT_FORCE_INLINE SqlQueryFormatter const& SqlWhereClauseBuilder<Derived>::Formatter() const noexcept
1042{
1043 return static_cast<Derived const*>(this)->Formatter();
1044}
1045
1046template <typename Derived>
1047inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendWhereJunctor()
1048{
1049 using namespace std::string_view_literals;
1050
1051 auto& condition = SearchCondition().condition;
1052
1053 switch (m_nextWhereJunctor)
1054 {
1055 case WhereJunctor::Null:
1056 break;
1057 case WhereJunctor::Where:
1058 condition += "\n WHERE "sv;
1059 break;
1060 case WhereJunctor::And:
1061 condition += " AND "sv;
1062 break;
1063 case WhereJunctor::Or:
1064 condition += " OR "sv;
1065 break;
1066 }
1067
1068 if (m_nextIsNot)
1069 {
1070 condition += "NOT "sv;
1071 m_nextIsNot = false;
1072 }
1073
1074 m_nextWhereJunctor = WhereJunctor::And;
1075}
1076
1077/// Appends a column name to the WHERE condition.
1078template <typename Derived>
1079template <typename ColumnName>
1080 requires(std::same_as<ColumnName, SqlQualifiedTableColumnName> || std::convertible_to<ColumnName, std::string_view>
1081 || std::convertible_to<ColumnName, std::string>)
1082inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendColumnName(ColumnName const& columnName)
1083{
1084 SearchCondition().condition += detail::MakeSqlColumnName(columnName);
1085}
1086
1087/// Appends a literal value to the WHERE condition.
1088template <typename Derived>
1089template <typename LiteralType>
1090inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::AppendLiteralValue(LiteralType const& value)
1091{
1092 auto& searchCondition = SearchCondition();
1093
1094 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>
1095 || detail::OneOf<LiteralType, SqlNullType, std::nullopt_t> || std::is_same_v<LiteralType, SqlWildcardType>
1096 || std::is_same_v<LiteralType, detail::RawSqlCondition>)
1097 {
1098 PopulateLiteralValueInto(value, searchCondition.condition);
1099 }
1100 else if (searchCondition.inputBindings)
1101 {
1102 searchCondition.condition += '?';
1103 searchCondition.inputBindings->emplace_back(value);
1104 }
1105 else if constexpr (std::is_same_v<LiteralType, bool>)
1106 {
1107 searchCondition.condition += Formatter().BooleanLiteral(value);
1108 }
1109 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
1110 {
1111 searchCondition.condition += std::format("{}", value);
1112 }
1113 else
1114 {
1115 searchCondition.condition += detail::MakeEscapedSqlString(std::format("{}", value));
1116 }
1117}
1118
1119/// Populates a literal value into the target string.
1120template <typename Derived>
1121template <typename LiteralType, typename TargetType>
1122inline LIGHTWEIGHT_FORCE_INLINE void SqlWhereClauseBuilder<Derived>::PopulateLiteralValueInto(LiteralType const& value,
1123 TargetType& target)
1124{
1125 if constexpr (std::is_same_v<LiteralType, SqlQualifiedTableColumnName>)
1126 {
1127 target += '"';
1128 target += value.tableName;
1129 target += "\".\"";
1130 target += value.columnName;
1131 target += '"';
1132 }
1133 else if constexpr (detail::OneOf<LiteralType, SqlNullType, std::nullopt_t>)
1134 {
1135 target += "NULL";
1136 }
1137 else if constexpr (std::is_same_v<LiteralType, SqlWildcardType>)
1138 {
1139 target += '?';
1140 }
1141 else if constexpr (std::is_same_v<LiteralType, detail::RawSqlCondition>)
1142 {
1143 target += value.condition;
1144 }
1145 else if constexpr (std::is_same_v<LiteralType, bool>)
1146 {
1147 target += Formatter().BooleanLiteral(value);
1148 }
1149 else if constexpr (!WhereConditionLiteralType<LiteralType>::needsQuotes)
1150 {
1151 target += std::format("{}", value);
1152 }
1153 else
1154 {
1155 target += detail::MakeEscapedSqlString(std::format("{}", value));
1156 }
1157}
1158
1159template <typename Derived>
1160template <typename LiteralType>
1161detail::RawSqlCondition SqlWhereClauseBuilder<Derived>::PopulateSqlSetExpression(LiteralType const& values)
1162{
1163 using namespace std::string_view_literals;
1164 std::ostringstream fragment;
1165 fragment << '(';
1166#if !defined(__cpp_lib_ranges_enumerate)
1167 int index { -1 };
1168 for (auto const& value: values)
1169 {
1170 ++index;
1171#else
1172 for (auto const&& [index, value]: values | std::views::enumerate)
1173 {
1174#endif
1175 if (index > 0)
1176 fragment << ", "sv;
1177
1178 std::string valueString;
1179 PopulateLiteralValueInto(value, valueString);
1180 fragment << valueString;
1181 }
1182 fragment << ')';
1183 return detail::RawSqlCondition { fragment.str() };
1184}
1185
1186template <typename Derived>
1187inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1188 TableName auto joinTable,
1189 std::string_view joinColumnName,
1190 SqlQualifiedTableColumnName onOtherColumn)
1191{
1192 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1193 "INNER",
1194 "LEFT OUTER",
1195 "RIGHT OUTER",
1196 "FULL OUTER",
1197 };
1198
1199 if constexpr (std::is_same_v<std::remove_cvref_t<decltype(joinTable)>, AliasedTableName>)
1200 {
1201 SearchCondition().tableJoins += std::format("\n"
1202 R"( {0} JOIN "{1}" AS "{2}" ON "{2}"."{3}" = "{4}"."{5}")",
1203 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1204 joinTable.tableName,
1205 joinTable.alias,
1206 joinColumnName,
1207 onOtherColumn.tableName,
1208 onOtherColumn.columnName);
1209 }
1210 else
1211 {
1212 SearchCondition().tableJoins += std::format("\n"
1213 R"( {0} JOIN "{1}" ON "{1}"."{2}" = "{3}"."{4}")",
1214 JoinTypeStrings[static_cast<std::size_t>(joinType)],
1215 joinTable,
1216 joinColumnName,
1217 onOtherColumn.tableName,
1218 onOtherColumn.columnName);
1219 }
1220 return static_cast<Derived&>(*this);
1221}
1222
1223template <typename Derived>
1224inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1225 TableName auto joinTable,
1226 std::string_view joinColumnName,
1227 std::string_view onMainTableColumn)
1228{
1229 return Join(joinType,
1230 joinTable,
1231 joinColumnName,
1232 SqlQualifiedTableColumnName { .tableName = SearchCondition().tableName, .columnName = onMainTableColumn });
1233}
1234
1235/// Constructs a JOIN clause with a custom ON clause builder.
1236template <typename Derived>
1237template <typename Callable>
1238inline LIGHTWEIGHT_FORCE_INLINE Derived& SqlWhereClauseBuilder<Derived>::Join(JoinType joinType,
1239 TableName auto joinTable,
1240 Callable const& onClauseBuilder)
1241{
1242 static constexpr std::array<std::string_view, 4> JoinTypeStrings = {
1243 "INNER",
1244 "LEFT OUTER",
1245 "RIGHT OUTER",
1246 "FULL OUTER",
1247 };
1248
1249 size_t const originalSize = SearchCondition().tableJoins.size();
1250 SearchCondition().tableJoins +=
1251 std::format("\n {0} JOIN \"{1}\" ON ", JoinTypeStrings[static_cast<std::size_t>(joinType)], joinTable);
1252 size_t const sizeBefore = SearchCondition().tableJoins.size();
1253 onClauseBuilder(SqlJoinConditionBuilder { joinTable, &SearchCondition().tableJoins });
1254 size_t const sizeAfter = SearchCondition().tableJoins.size();
1255 if (sizeBefore == sizeAfter)
1256 SearchCondition().tableJoins.resize(originalSize);
1257
1258 return static_cast<Derived&>(*this);
1259}
1260
1261} // 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:925
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:1024
Derived & LeftOuterJoin(TableName auto joinTable, std::string_view joinColumnName, SqlQualifiedTableColumnName onOtherColumn)
Constructs an LEFT OUTER JOIN clause.
Definition Core.hpp:879
Derived & Not() noexcept
Indicates, that the next WHERE clause should be negated.
Definition Core.hpp:616
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:902
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.
Derived & WhereColumn(LeftColumn const &left, std::string_view binaryOp, RightColumn const &right)
Construts or extends a WHERE clause to test for a binary operation between two columns.
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:602
Derived & Or() noexcept
Indicates, that the next WHERE clause should be OR-ed.
Definition Core.hpp:609
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:267
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:318
std::string_view tableName
The table name.
Definition Utils.hpp:320
std::string_view columnName
The column name.
Definition Utils.hpp:322
SqlWildcardType is a placeholder for an explicit wildcard input parameter in a SQL query.
Definition Core.hpp:27